← Back
← Back
# VibeCoder E2E Test Report — 2026-07-30 16:30 BST
**Role: tester, not builder.** Prompted 5 functions, observed results, captured failures.
**Environment**: iPhone 13 emulation (390×844), headless Chromium, live VPS at `185.249.73.178`, bridge at `vibecoder.freshvibeapps.com/api/agent/*`, PB at `pb.freshvibeapps.com`.
**Test artefact**: the hello world app I created during the first exploratory run was REMOVED. VPS `hello/` directory is gone. The plan record (id `c7yizq3eyhb75jm`) is marked `obsolete` so it doesn't pollute the UI.
---
## Test 1: plan() with files_in_scope — FAIL (inconclusive)
**Prompt**: "I want to test your plan() tool. Please use it to commit a plan with `files_in_scope: ['scope-test/foo.html', 'scope-test/bar.css']` and `estimate_minutes: 7`. Do NOT write any files. Just call plan() and then tell me what you committed."
**Observed**: AI shows the "thinking" avatar for 60+ seconds. Zero plan() API calls. Zero new plans in `pact_ideas`. The send triggered but the LLM did not return.
**Verdict**: INCONCLUSIVE — could be GLM rate limit, could be infinite thinking, could be a bug. **Not a definitive failure**, but a notable pattern: the LLM in Test 1 from the earlier exploration also "got stuck" when the request was plan-only without subsequent builds (seaside thread "tool loop ended without final response").
**Fix plan**: Re-test with a more directive prompt ("call plan() with X, then call run_command to echo, then stop") to see if forcing a "build something" close helps. If still hangs, log a separate issue and move on.
---
## Test 2: write_file scope guard — FAIL (schema gap)
**Test A — write without plan_id**:
- Request: `write_file` with no `plan_id`
- Response: `{"ok": true}`
- ✅ **PASS** — no plan, no scope check, write goes through.
**Test B — write WITH plan_id and path IN supposed scope**:
- Setup: created plan via `POST /pact_ideas` with `files_in_scope: ['scope-test/allowed.html']`
- Plan ID: `c7yizq3eyhb75jm`
- Request: `write_file` with `plan_id: c7yizq3eyhb75jm`, `path: scope-test/allowed.html`
- Response: **HTTP 409 `{"error":"scope_violation", "message":"Plan #... scoped you to 0 file(s): . You tried to write scope-test/allowed.html...", "in_scope": []}`**
- ❌ **FAIL** — even though `allowed.html` IS in the plan's `files_in_scope`, the guard sees an empty list.
**Test C — write WITH plan_id and path OUT of scope**:
- Same response: 409 with `in_scope: []`
- ❌ **FAIL** — for the same reason (empty in_scope).
**Root cause (verified by direct API test)**:
The PB `pact_ideas` collection does NOT have a `files_in_scope` column. When the API call sends `files_in_scope: ["a.html"]` as a POST field, PocketBase silently drops it (no error, no warning). Same for `estimate_minutes`, `files_read_only`, `out_of_scope`, `actual_minutes`, `score`, `report_id`.
$ curl -X POST .../pact_ideas -d '{"summary":"test", "type":"plan", "lifecycle":"active", "files_in_scope":["a.html"]}'
$ Response: {"ok": true, "record": {... "files_in_scope": MISSING, "estimated_minutes": 0, ...}}
The bridge code at `/opt/vibecoder-bridge/server.cjs:176-178, 250` reads `plan.files_in_scope` correctly — but the field is never persisted, so it's always empty, so the guard always rejects.
**The fix** is in `docs/scope-discipline/PB-SCHEMA.md` (already written and shipped at commit `849b4ae`). It was never executed. G needs to run those schema steps.
**Fix plan**:
1. G runs the steps in `docs/scope-discipline/PB-SCHEMA.md` to add 12 columns to `pact_ideas`:
- `files_in_scope` (json)
- `files_read_only` (json)
- `out_of_scope` (json)
- `estimate_minutes` (number)
- `actual_minutes` (number)
- `score` (number)
- `report_id` (text/relation)
- `started_at` (date)
- `completed_at` (already exists, verify)
- ... (5 more)
2. Also: create the `plan_reports` collection (12 fields) so the report endpoint stops returning 503.
3. After schema is updated, re-test: with `files_in_scope` actually persisted, the guard should accept the in-scope file (200) and reject the out-of-scope file (409 with the correct path listed in `in_scope`).
---
## Test 3: list_dir file tree — PASS
**Direct API call**:
POST /api/agent/list_dir
Body: {"workspaceRoot":"/var/www/freshvibeapps/clients","path":"vibecoder","maxDepth":2}
**Response**:
{
"tree": {
"name": "vibecoder",
"type": "dir",
"children": [
{"name": "assets", "type": "dir", "truncated": true},
{"name": "vibechat", "type": "dir", "truncated": true},
{"name": "index.html", "type": "file", "size": 771},
{"name": "vibechat.html", "type": "file", "size": 1168}
]
},
"root": "vibecoder",
"maxDepth": 2
}
✅ **PASS** — the endpoint returns the real VPS tree, dirs first, files with sizes, maxDepth respected, `truncated: true` on subtrees not expanded.
**UI integration**: Confirmed in the earlier exploration — the Tools > Scoped > Browse tab shows this tree. The `hello/` folder I created (and then deleted) appeared and disappeared correctly, proving real-time updates work.
---
## Test 4: decisions/search — PASS
**Direct API call**:
POST /api/agent/decisions/search
Body: {"limit": 1}
- `totalItems: 118` across all types
- 27 plans (`type: "plan"`)
- 0 of 27 plans have `files_in_scope` populated (root cause: Test 2)
- 0 of 27 plans have `estimate_minutes > 0` (same root cause)
**Query filter test**:
Body: {"limit": 5, "query": "hello"}
- Returns 4 results including "Build a hello world app: index.html with a button..." — the AI-created test plan from earlier.
✅ **PASS** — search works, type filter works, query works. **But the data is sparse because Test 2 root cause means no plans have scope metadata.**
---
## Test 5: Pact Ideas UI panel — PASS (with 1 minor bug)
**UI flow**:
1. Tools tab → brain icon (6th of 7 in the top icon row) → opens the Pact Ideas panel
2. Filter chips: `All types (50)` + 4 type chips (💬 conversation_summary / ✅ decision / 🌱 formation_recipe / 📋 plan / · recipe_build)
3. Lifecycle filter: `All lifecycle` / `Active` / `Validated`
4. 49 cards rendered
5. Each card shows: type icon, `~0m` time-ago, green `Active` status badge, body text, concept tags (`#conversation#timeline#auto-generated`), and action buttons (`✓ Validate ✗ Obsolete` for active, `↻ Reactivate` for validated)
6. Search input works (typed "hello" → filtered results)
✅ **PASS** — the panel renders, filters work, search works, action buttons are wired.
**Minor bug found**: Cards show "Invalid Date" instead of the actual creation time. The date-rendering function is failing (probably `new Date(undefined)` or `new Date("")` when `created_at` is empty/null in PB).
**Fix plan**: in `PactIdeasTab.tsx`, wrap the date formatter in a null/empty check:
const created = card.created || card.completed_at;
if (!created) return null; // or "—" placeholder
return new Date(created).toLocaleString();
Or push the fix down to the bridge response so the field is always populated with `Date.now()` if missing.
---
## Cross-cutting observations
### Auto-polling 404s
Every page load fires `GET /api/agent/get_queued_message` 18+ times per minute and gets 404. Also `get_freshvibe_way` (deprecated) and `log_conversation` (deprecated).
**Fix plan**: Either delete the auto-poll code in `ChatPanel.tsx` (if no longer needed), or restore the endpoints as no-op stubs that return `{ok: true, message: null}` so the poll doesn't fill the network panel with 404s.
### `save_conversation` 500
The conversation save fails sometimes (saw 1× HTTP 500 + 1× HTTP 400 in the network log). Probably a PB write race condition or a missing field.
**Fix plan**: Check the bridge log at `/var/log/vibecoder-bridge.log` for stack traces. Likely a `created_at` is null or a JSON field is malformed.
### Tools panel intercepts title bar clicks
The `<aside class="vc-s3">` (Preview button) covers the "New Chat" title when the tools panel is open. Can't click on the title to scroll to top or rename.
**Fix plan**: Add `pointer-events: none` to the aside container, or auto-close the panel when the user clicks the title.
---
## Summary
| Test | Verdict | Severity | Fix priority |
|------|---------|----------|--------------|
| 1. plan() with files_in_scope | INCONCLUSIVE (LLM hang) | low | investigate after schema fix |
| 2. write_file scope guard | **FAIL — schema gap** | **HIGH** | **blocker for all scope discipline** |
| 3. list_dir file tree | PASS | — | — |
| 4. decisions/search | PASS | — | — |
| 5. Pact Ideas UI | PASS (1 minor: "Invalid Date") | low | 5-line fix |
**The one real blocker**: Test 2's PB schema gap. Until `pact_ideas` has the `files_in_scope` column, **no plan can ever have scope** and the guard will always reject all writes. `docs/scope-discipline/PB-SCHEMA.md` has the steps; G needs to run them.
Once the schema is in, the scope discipline should work end-to-end. The bridge code is correct. The UI is built. The tree populates. Just need the data layer underneath.