Problem
/work and /work-status both resolve the project root from process.cwd() instead of the ctx.cwd that Pi hands the command handler:
// extension/src/commands.ts:191
const cwd = process.cwd();
const repoRoot = await resolveRepoRoot(cwd);
// extension/src/work-status.ts:362
const cwd = process.cwd();
const repoRoot = await resolveRepoRoot(cwd);
ExtensionContext.cwd: string is first-class Pi API (@earendil-works/pi-coding-agent core/extensions/types.d.ts:216), and resolveRepoRoot(cwd) already takes the parameter — the plumbing exists, it is just fed the wrong value. Whenever Pi's process cwd differs from the session cwd, /work writes its state file into the wrong repo.
Evidence — the offline smoke suite writes into the live state directory
Because the handler ignores the context it is given, smoke-tests/test-command-flow.ts cannot isolate itself. Its /work invocations (test-command-flow.ts:182,234,263,272,285,297) resolve to the real repo root, and every run of the standard pre-push gate leaves fixtures behind:
$ ls .pi/work-state/
547.json 548.json 549.json 551.json 561.json 789.json <- test residue
5.json 208.json 277.json 279.json ... <- real cycles
These sit at "status":"running" with an empty eventLog, so a real /work 547 would collide with a fixture. 561.json even carries "issues":[561,562,563] — the exact multi-issue shape work-driver-context.ts:139 cites as the historical false-MERGED incident.
Sibling tests already do this correctly with mkdtempSync (test-work-driver-pr5.ts:145); test-command-flow.ts is the outlier only because production gives it no seam to use.
Fix
commands.ts:191 and work-status.ts:362 — use ctx.cwd instead of process.cwd().
test-command-flow.ts — makeCtx() takes a cwd; the /work cases pass a mkdtempSync dir so the suite stops touching the real state directory.
- Delete the six committed-by-accident fixture files from
.pi/work-state/.
Acceptance criteria
Out of scope
spawn.ts:162 (spec.cwd ?? process.cwd()) — correct fallback, no ctx available.
permission-*.ts, session-autosave.ts — run outside command-handler context where the process cwd is genuinely the right answer.
Problem
/workand/work-statusboth resolve the project root fromprocess.cwd()instead of thectx.cwdthat Pi hands the command handler:ExtensionContext.cwd: stringis first-class Pi API (@earendil-works/pi-coding-agentcore/extensions/types.d.ts:216), andresolveRepoRoot(cwd)already takes the parameter — the plumbing exists, it is just fed the wrong value. Whenever Pi's process cwd differs from the session cwd,/workwrites its state file into the wrong repo.Evidence — the offline smoke suite writes into the live state directory
Because the handler ignores the context it is given,
smoke-tests/test-command-flow.tscannot isolate itself. Its/workinvocations (test-command-flow.ts:182,234,263,272,285,297) resolve to the real repo root, and every run of the standard pre-push gate leaves fixtures behind:These sit at
"status":"running"with an emptyeventLog, so a real/work 547would collide with a fixture.561.jsoneven carries"issues":[561,562,563]— the exact multi-issue shapework-driver-context.ts:139cites as the historical false-MERGED incident.Sibling tests already do this correctly with
mkdtempSync(test-work-driver-pr5.ts:145);test-command-flow.tsis the outlier only because production gives it no seam to use.Fix
commands.ts:191andwork-status.ts:362— usectx.cwdinstead ofprocess.cwd().test-command-flow.ts—makeCtx()takes a cwd; the/workcases pass amkdtempSyncdir so the suite stops touching the real state directory..pi/work-state/.Acceptance criteria
process.cwd()remains in a registered command handler that hasctxin scope.git status --porcelainempty and creates no new.pi/work-state/*.json.test-command-flow.tsasserts the work-state path it was given, not the ambient one.Out of scope
spawn.ts:162(spec.cwd ?? process.cwd()) — correct fallback, no ctx available.permission-*.ts,session-autosave.ts— run outside command-handler context where the process cwd is genuinely the right answer.