Skip to content

fix(mcp): pin PWD to cwd when spawning a local harness#542

Merged
drewstone merged 2 commits into
mainfrom
fix/mcp-pin-pwd-local-harness
Jul 13, 2026
Merged

fix(mcp): pin PWD to cwd when spawning a local harness#542
drewstone merged 2 commits into
mainfrom
fix/mcp-pin-pwd-local-harness

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Root cause

runLocalHarness spawns the coding CLI with cwd set to the target worktree, but it inherited the parent process's PWD. Harnesses that resolve their working directory from $PWD rather than getcwd() — opencode does; the others may — then read and edit the wrong directory (the parent's), not the worktree they were placed in.

The worktree diff comes back empty and the delegation fails with a phantom empty patch — no candidate passed validation, even though the harness ran and "succeeded" against the parent dir.

Fix

Pin PWD to cwd in the child env. On main, the spawn env is baseEnv, which feeds both the codex-isolated path (isolateCodexHome) and the plain-spawn path, so pinning it once at the source covers every runLocalHarness consumer:

const baseEnv: NodeJS.ProcessEnv = { ...(options.env ?? process.env), PWD: cwd }

The explicit NodeJS.ProcessEnv annotation preserves the ProcessEnv index signature — without it the object-literal spread widens the type and later env.CODEX_HOME reads on the isolated path stop type-checking.

Proof

  • tsc --noEmit: zero errors in src/mcp/local-harness.ts with the change.
  • Behavioral: this fix was extracted while debugging the loops pi supervisor, where the local (opencode) worker reported success but the workspace changed not at all. With PWD pinned, the harness edits the worktree it was handed and the diff is non-empty — the delegation now delivers.
  • Merges cleanly into origin/main (git merge-tree, zero conflicts).

Notes

Origin: commit 8a4a1461 on feat/with-intelligence (a stale branch). That branch's local-harness.ts predates main's baseEnv refactor, so this is a clean re-port of the same one-line intent onto current main, isolated on a fresh branch off origin/main — no unrelated feat/with-intelligence work is dragged in.

🤖 Generated with Claude Code

runLocalHarness spawns the coding CLI with `cwd` set to the worktree, but
inherited the parent process's `PWD`. Harnesses that resolve their working
directory from `$PWD` rather than getcwd() (opencode does) then edit the
WRONG directory, so the worktree diff comes back empty and the delegation
fails with a phantom "empty patch — no candidate passed validation".

Merge `PWD: cwd` into the child env so the harness edits where it was placed.

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — ce1ece28

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-13T06:18:10Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Value Audit — sound

Verdict sound
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 173.2s (2 bridge agents)
Total 173.2s

💰 Value — sound

Pins PWD=cwd in the harness spawn env so harnesses that read $PWD instead of getcwd() edit the worktree they were placed in — correct, minimal, well-placed.

  • What it does: Sets PWD to match the cwd spawn option in the child process environment for runLocalHarness, so a harness CLI that resolves its working directory from the $PWD env var (opencode does) operates in the correct worktree rather than the parent's directory.
  • Goals it achieves: Eliminates the 'empty patch / no candidate passed validation' failure where a harness runs and reports success but edits nothing, because it was editing the parent's directory (stale inherited PWD) while the executor diffed the worktree it was spawned into.
  • Assessment: Correct one-line fix placed at exactly the right point — baseEnv is the single source that feeds both the codex-isolated path (isolateCodexHome spreads it at line 1047, and PWD survives the sensitive-env stripping) and the plain-spawn else branch (line 429). The NodeJS.ProcessEnv annotation is necessary, not decorative — without it the spread widens and later env.CODEX_HOME reads inside isol
  • Better / existing approach: none — this is the right approach. Searched all spawn sites in src/ (in-process-executor.ts:233, worktree-harness.ts:299, runtime/supervise/runtime.ts:779, mcp-serve-verifier.ts:50, local-harness.ts:836) — none has PWD-pinning logic to reuse, and no shared spawn-env helper exists. The sh-based spawners use getcwd() so they don't need it; only harness CLIs that may read $PWD do. A shared helper wou
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 2
  • Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content

🎯 Usefulness — sound

Correct one-line fix that pins PWD to the spawn cwd at the single env source feeding every runLocalHarness consumer, closing a real bug where harnesses that read $PWD edit the parent dir instead of their worktree.

  • Integration: Fully reachable. runLocalHarness is consumed by two production paths — runWorktreeHarness (src/mcp/worktree-harness.ts:192) and agenticGenerator (src/improvement/agentic-generator.ts:103) — and neither passes options.env, so both inherit process.env and land on the fixed baseEnv line. The in-process executor and worktree CLI executor both route through runWorktreeHarness, so the fix reaches every
  • Fit with existing patterns: Matches the codebase's grain exactly. baseEnv at src/mcp/local-harness.ts:419 was already the one env source feeding both the codex-isolated path (isolateCodexHome, which re-spreads baseEnv at line 1047) and the plain-spawn fallback (line 429). Pinning PWD once at that source is the minimal, correct place — no competing pattern, no second env-construction site to keep in sync. The explicit NodeJS.
  • Real-world viability: Holds on all paths. PWD:cwd is always correct because the spawn always uses cwd (line 473), so there is no input where the pin is wrong. options.env override is respected (spread first, PWD last wins). For codex reproducible mode the pin survives isolation since isolateCodexHome copies baseEnv into env at line 1047 and PWD is not in the sensitive-name exclude filter (line 1050). No concurrency sur
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

No concerns — sound change, no better or existing approach found. ✅


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260713T062251Z

@tangletools

Copy link
Copy Markdown
Contributor

✅ No Blockers — ce1ece28

Review health 100/100 · Reviewer score 89/100 · Confidence 65/100 · 2 findings (2 low)

glm: Correctness 89 · Security 89 · Testing 89 · Architecture 89

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 1/1 planned shots over 1 changed files. Global verifier still owns final merge decision.

🟡 LOW No regression test pins PWD to cwd — src/mcp/local-harness.ts

The fix is the kind of silent failure it claims to fix (phantom 'empty patch' from a stale $PWD), yet tests/mcp/local-harness.test.ts asserts env.CODEX_HOME and env.OPENAI_API_KEY removal but never asserts opts.env.PWD === cwd. A future refactor could drop the pin and reintroduce the bug with zero test signal. Add expect(opts.env.PWD).toBe(cwd) to the existing spawn-assertion test (e.g., the codex isolation test at line ~257 or the claude run at line ~211).

🟡 LOW PWD assigned without resolve() — relative cwd yields relative PWD — src/mcp/local-harness.ts

PWD conventionally holds an absolute path. spawn already accepts relative cwd (resolved against parent cwd), but a harness reading $PWD verbatim would get a relative value if a caller passed one. Worktree paths are absolute in practice so impact is nil; optional hardening: PWD: resolve(cwd).


tangletools · 2026-07-13T06:27:21Z · trace

tangletools
tangletools previously approved these changes Jul 13, 2026

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved — 2 non-blocking findings — ce1ece28

Full multi-shot audit completed 1/1 planned shots over 1 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-07-13T06:27:21Z · immutable trace

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — ed7c6675

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-13T20:14:57Z

@drewstone drewstone merged commit 6648161 into main Jul 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants