Worktree isolation inflates spawned-shell env past ARG_MAX → E2BIG on all Bash, unrecoverable without restart

Status Fixed / completed
Maintainer reply ✓ Yes — bcherny
Activity 4 comments · opened Jul 3, 2026 · closed Aug 17, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Summary

After dispatching a subagent with isolation: 'worktree', every subsequent Bash tool call in the parent session fails with:

E2BIG: argument list too long, posix_spawn '/bin/zsh'

No shell can be spawned for the rest of the session — even echo ok fails identically. Non-shell tools (Read/Edit/Write/Grep) keep working because they don't spawn a shell. There is no in-session recovery; only a full CLI restart clears it.

Environment

  • Claude Code v2.1.200
  • macOS (Darwin), ARG_MAX ≈ 1 MB
  • Model: Opus 4.8
  • A project with many auto-registered skills (~38 in the repo where this reproduced), each carrying a full description
  • Multi-agent worktree workflow (an "EM" parent session dispatches "IC" subagents with isolation: 'worktree')

Root cause (hypothesis)

Creating the worktree triggers auto-registration of the repo's skills a second time, scoped to the worktree path (e.g. .claude/worktrees/agent-<id>:<skill>), each carrying its full description. These skill descriptions appear to be injected into the spawned shell's environment (envp). The duplicated skill set pushes total envp size over ARG_MAX, so posix_spawn('/bin/zsh') fails before it can run anything.

Evidence:

  • Bash worked fine before the worktree was created; the first failure immediately followed the worktree's skill-registration dump (a system message re-listing all skills scoped to the worktree path).
  • The failure is on shell spawn (posix_spawn '/bin/zsh') — i.e. argv/envp size — not on the command; echo ok fails the same way.
  • Non-shell tools are unaffected, consistent with a limit hit only at process-spawn time.
  • Follow-on: stale worktrees left on disk from a prior session re-register on the next session's startup, so a fresh session can begin already near ARG_MAX and see intermittent E2BIG on longer commands before it ever dispatches a worktree agent. (In the repro repo, ~60 worktrees had accumulated across sessions.)

Reproduction

  1. Open a session in a repo with many skills (~38).
  2. Dispatch a subagent with isolation: 'worktree'.
  3. After the worktree registers its scoped skills, run any Bash call in the parent.

→ E2BIG: argument list too long, posix_spawn.

Accumulation variant: leave several worktrees on disk, start a fresh session, and observe intermittent E2BIG on longer commands from the start.

Impact

Total loss of shell-dependent work mid-session, with no in-session recovery:

  • gh (PR review, issue filing, comments, merge), git, the test suite / tsc / lint, /code-review, /security-review — anything that spawns a shell.
  • For a multi-agent (EM/IC) workflow this is severe: the parent can dispatch a worktree agent and read its diff, but then can't run review gates, update GitHub, dispatch the next wave, or merge — the session strands at the review gate and a human has to intervene.

Recovery today (restart only)

  • Pruning the worktree on disk (git worktree remove <path> --force) does not restore Bash — the running process's envp was already inflated at spawn time; removing the worktree afterward doesn't shrink the live process's environment.
  • /clear does not help (same OS process, same envp).
  • Only a full CLI restart recovers (claude --continue / claude --resume spawns a fresh process with a clean environment). With the worktree already pruned, the fresh session also won't re-register the scoped skills.

Proposed fixes

  1. Keep skill metadata out of the spawned shell's environment. Skill descriptions are agent-context data, not shell env; passing them via envp is what blows ARG_MAX. Move them out of the spawned process's environment entirely.
  2. Don't re-register the full skill set for a worktree of the same repo. A worktree already has the repo's skills; the second, path-scoped registration is what doubles the payload.
  3. Prune stale worktree skill registrations on session startup so a fresh session doesn't inherit a pre-inflated env from a prior session's leftover worktrees.
  4. Guard the spawn. Detect envp approaching ARG_MAX and fail loudly with a clear message + self-recovery hint, instead of an opaque E2BIG on every shell call.
  5. Allow an in-session env reset — a way to shrink/reset the tool environment without a full restart would turn a session-ending failure into a hiccup.

Workaround

Keeping .claude/worktrees/ pruned (removing worktrees whose branches are merged + clean) relieves the pressure, but it's mitigation, not a fix — the core issue is that skill metadata is being passed through the spawned shell's envp at all, and that a same-repo worktree re-registers its skills.

View original on GitHub ↗

3 Comments

yurukusa · 3 months ago

Your root-cause hypothesis holds up against the mechanics. posix_spawn has to fit envp + argv inside ARG_MAX (≈1 MB on macOS, ≈2 MB on Linux — getconf ARG_MAX). The tells you listed are exactly what an envp-overflow looks like, not a command-length one:

  • it fails on spawn (posix_spawn '/bin/zsh'), so it's argv/envp size, not the command — which is why echo ok dies identically;
  • non-shell tools survive because they never posix_spawn;
  • it appears right after the worktree's skill re-registration dump.

So a second, worktree-scoped copy of ~38 full skill descriptions landing in envp is a very plausible way to cross a 1 MB ceiling — the descriptions are the big, duplicated payload.

Things that help until the injection is fixed server-side:

Measure how close you already are. Before it bites, compare env size to the limit:

# Linux: exact envp bytes
echo "envp=$(wc -c </proc/self/environ)  ARG_MAX=$(getconf ARG_MAX)"
# macOS (no /proc): rough proxy
printf '%s\0' "$(env)" | wc -c ; getconf ARG_MAX

If envp is already a large fraction of ARG_MAX at rest, worktree isolation (which roughly doubles the skill payload) is what tips it over. Repos with many always-registered skills are the ones at risk.

Cut the payload or skip the isolation.

  • Trim auto-registered skills to what the task needs, or shorten the longest skill descriptions — envp shrinks proportionally, and the doubled copy then still fits.
  • For a skill-heavy repo, avoid isolation: 'worktree' on the dispatch until this is fixed; the flat (non-worktree) path doesn't re-register the second scoped copy.
  • Once you're wedged there's no in-session recovery (you confirmed this) — a CLI restart is the only clear, because the oversized envp is baked into the parent process.

Honest limits: all of the above are you working around a payload you didn't choose to put there. The durable fix is server-side — skill descriptions shouldn't ride in envp of spawned shells at all (a file/fd or an in-process channel avoids the ARG_MAX ceiling entirely), and worktree creation shouldn't register a second full copy scoped to the worktree path. Until then, "keep resting envp well under half of ARG_MAX" is the practical guardrail for anyone running worktree fan-out on a skill-rich repo.

zenprocess · 2 months ago

Since posix_spawn has to fit envp + argv together inside ARG_MAX, it's worth ruling out the argv side too while chasing the env-side inflation here — they draw on the same budget, so a large argv makes the env threshold easier to trip (and vice-versa).

On the argv side, the inline sandbox profile derives write-deny paths from the registered projects in ~/.claude.json, and those entries are never removed when their directory is deleted. In a multi-worktree / subagent-isolation workflow that creates and tears down many dirs, they accumulate unbounded. One setup had ~1,250 projects entries, ~92% pointing at deleted dirs (~1 MB of argv on their own) — enough to trip E2BIG with very few live worktrees. Pruning the missing-dir entries restored Bash immediately.

A cheap general guard for the argv half: skip any deny-path whose directory no longer exists (one isdir() filter). Details/measurements: https://github.com/anthropics/claude-code/issues/73437#issuecomment-5095540627

bcherny collaborator · 1 month ago

Thanks for the detailed repro steps. I ran them on 2.1.233 on Linux: a fresh repo with 40 skills (~4 KB descriptions each), then a worktree-isolated subagent, then re-checked the shell. env | wc -c was byte-identical before and after (8523), the CLAUDE* var count didn't change, and echo ok kept working. So skill descriptions don't end up in the spawned shell's environment — the env-inflation mechanism in the report isn't what was happening.

The E2BIG failure itself was real, though, with a different cause: in sandboxed sessions the spawned command line grew with the number of registered git worktrees (entries in the git worktree registry survive deleting the checkout directories, so heavy worktree-isolation use accumulates them), and part of that list was duplicated. This was fixed in 2.1.203 — "Fixed Bash failing with 'argument list too long' in repos with many git worktrees" (changelog). Since then the duplication is gone, and if a session ever does hit the OS limit, Bash reports exact size accounting and recovery steps (e.g. git worktree prune) instead of the raw posix_spawn error, so it's no longer unrecoverable.

Closing as fixed — if you still hit this on 2.1.203 or later, reply here (ideally with git worktree list | wc -l from the affected repo) and we'll reopen.

🤖 Generated with Claude Code

Showing cached comments. Read the full discussion on GitHub ↗