Skip to content

Fan-out session wrote and committed outside its ClaudeAgentOptions cwd #161

Description

@sebyx07

Observed twice while verifying the SDK 0.2.150 upgrade, with opposite outcomes on identical setups — so this is reported as nondeterministic and needing confirmation, not as a settled diagnosis.

What happened

A harness ran AgentWrapper.run_work_session(..., parallel=True) against a scratch project, with working_dir (and therefore ClaudeAgentOptions.cwd) set to /tmp/.../hive-verify. The parent Python process's own cwd was the claude-task-master checkout.

The scratch project contained src/alpha.py, src/beta.py, src/gamma.py; the task was to implement run() in each.

  • Run 1 — correct. A worker explicitly noted it resolved src/alpha.py to the absolute scratch path "since that is the actual working directory for this task", and the lead committed in the scratch repo.
  • Run 2 — wrong. The workers wrote src/alpha.py, src/beta.py and src/gamma.py into the claude-task-master checkout, and the lead then ran cd "$(git rev-parse --show-toplevel)" && git add … && git commit, producing a real commit (feat: add run() to alpha/beta/gamma modules) on the checked-out feature branch of the wrong repository. The scratch project was left untouched, still holding the raise NotImplementedError stubs.

Both runs used the same harness, same options, same launch directory. The only difference was a git reset --hard on the scratch repo between them.

Why it matters

CLAUDE.md is explicit that the working directory is passed to the SDK as cwd= and never chdir'd, precisely so concurrent queries and concurrent subagents are safe. If a session's tools can resolve relative paths — and git rev-parse --show-toplevel — against the parent process's cwd instead, then:

  • a run can write into, and commit to, a repository that is not its target;
  • the blast radius is whatever directory claudetm-server (or any embedding process) happens to be running in.

In normal CLI use claudetm start runs from inside the project, so process cwd and cwd= coincide and this cannot bite. It matters for server mode, for the REST /repo/* flows, and for anything embedding AgentWrapper with a working_dir other than its own cwd.

Not established

  • Whether the divergence is in the lead, the workers, or both (in run 2 the workers wrote to the wrong tree and the lead committed there).
  • Whether forward_subagent_text or the concurrency env var has any bearing (neither plausibly should).
  • Whether the SDK propagates cwd to subagent processes at all, or only to the parent — this is undocumented as far as the subagent docs go.

Suggested next step

A small deterministic probe: query() with cwd=<dir A> while the process sits in <dir B>, prompt = "dispatch two subagents; each runs pwd and git rev-parse --show-toplevel and reports". Repeat enough times to see whether the answer is stable. If subagents do not inherit cwd, the fan-out brief needs to state the absolute working directory explicitly, and the "the lead alone runs git" rule needs an absolute -C <dir>.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/backendsize/4h~half a day — substantial, design work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions