claude-code - 💡(How to fix) Fix Bash tool cwd forced to `/` when claude CLI is spawned from a non-shell parent in stream-json mode [1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#46985Fetched 2026-04-13 05:44:32
View on GitHub
Comments
1
Participants
1
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×5closed ×1commented ×1

When the claude CLI is spawned from a non-shell parent process (Node, Bun, Python, etc.) using --output-format stream-json --input-format stream-json, the Bash tool's subprocess always sees its working directory as /, regardless of the cwd passed to the spawner. The CLI's own init-time cwd is correct, so the divergence happens only at the Bash tool's subprocess spawn.

This breaks Agent SDK hosts that expect per-session working directories, and it also breaks automatic CLAUDE.md discovery from the session's nominal cwd.

Root Cause

When the claude CLI is spawned from a non-shell parent process (Node, Bun, Python, etc.) using --output-format stream-json --input-format stream-json, the Bash tool's subprocess always sees its working directory as /, regardless of the cwd passed to the spawner. The CLI's own init-time cwd is correct, so the divergence happens only at the Bash tool's subprocess spawn.

This breaks Agent SDK hosts that expect per-session working directories, and it also breaks automatic CLAUDE.md discovery from the session's nominal cwd.

Fix Action

Fix / Workaround

Agent SDK hosts that run the CLI as a programmatic subprocess (typical for chatbots, automations, Claude Agent SDK integrations) can't rely on cwd for their bash tool operations. Workarounds require either:

Neither is a proper fix; both are user-space patches for what looks like an internal chdir inside the CLI.

Code Example

// /tmp/sdk-cwd-test.mjs
import { spawn } from 'child_process';

const target = '/Users/me/project/subdir';
const proc = spawn('/Users/me/.local/bin/claude', [
  '-p',
  '--output-format', 'stream-json',
  '--verbose',
  '--input-format', 'stream-json',
  '--dangerously-skip-permissions',
], {
  cwd: target,
  stdio: ['pipe', 'pipe', 'inherit'],
  env: {
    ...process.env,
    ANTHROPIC_API_KEY: 'sk-ant-...',
  },
});

proc.stdin.write(JSON.stringify({
  type: 'user',
  message: { role: 'user', content: 'Run `pwd` and output only the path.' }
}) + '\n');
proc.stdin.end();

let out = '';
proc.stdout.on('data', c => out += c);
proc.on('close', () => {
  for (const line of out.split('\n')) {
    try {
      const d = JSON.parse(line);
      if (d.type === 'system' && d.subtype === 'init') console.log('INIT cwd:', d.cwd);
      if (d.type === 'result') console.log('Bash pwd:', d.result);
    } catch {}
  }
});

---

node /tmp/sdk-cwd-test.mjs

---

INIT cwd: /Users/me/project/subdir   ← correct
Bash pwd: /WRONG

---

cd /Users/me/project/subdir && \
  echo '{"type":"user","message":{"role":"user","content":"Run pwd and output only path"}}' | \
  /Users/me/.local/bin/claude -p --output-format stream-json --verbose --input-format stream-json --dangerously-skip-permissions

---

{"type":"result","result":"/Users/me/project/subdir", ...}
RAW_BUFFERClick to expand / collapse

Summary

When the claude CLI is spawned from a non-shell parent process (Node, Bun, Python, etc.) using --output-format stream-json --input-format stream-json, the Bash tool's subprocess always sees its working directory as /, regardless of the cwd passed to the spawner. The CLI's own init-time cwd is correct, so the divergence happens only at the Bash tool's subprocess spawn.

This breaks Agent SDK hosts that expect per-session working directories, and it also breaks automatic CLAUDE.md discovery from the session's nominal cwd.

Environment

  • OS: macOS 15 (Darwin 25.3.0), Apple Silicon
  • claude CLI: 2.1.104
  • Node: 22.22.0
  • @anthropic-ai/claude-agent-sdk: latest as of 2026-04-12

Reproduction

Failing case (Node spawn)

// /tmp/sdk-cwd-test.mjs
import { spawn } from 'child_process';

const target = '/Users/me/project/subdir';
const proc = spawn('/Users/me/.local/bin/claude', [
  '-p',
  '--output-format', 'stream-json',
  '--verbose',
  '--input-format', 'stream-json',
  '--dangerously-skip-permissions',
], {
  cwd: target,
  stdio: ['pipe', 'pipe', 'inherit'],
  env: {
    ...process.env,
    ANTHROPIC_API_KEY: 'sk-ant-...',
  },
});

proc.stdin.write(JSON.stringify({
  type: 'user',
  message: { role: 'user', content: 'Run `pwd` and output only the path.' }
}) + '\n');
proc.stdin.end();

let out = '';
proc.stdout.on('data', c => out += c);
proc.on('close', () => {
  for (const line of out.split('\n')) {
    try {
      const d = JSON.parse(line);
      if (d.type === 'system' && d.subtype === 'init') console.log('INIT cwd:', d.cwd);
      if (d.type === 'result') console.log('Bash pwd:', d.result);
    } catch {}
  }
});

Run:

node /tmp/sdk-cwd-test.mjs

Output:

INIT cwd: /Users/me/project/subdir   ← correct
Bash pwd: /                           ← WRONG

Working case (shell pipe)

cd /Users/me/project/subdir && \
  echo '{"type":"user","message":{"role":"user","content":"Run pwd and output only path"}}' | \
  /Users/me/.local/bin/claude -p --output-format stream-json --verbose --input-format stream-json --dangerously-skip-permissions

Output contains:

{"type":"result","result":"/Users/me/project/subdir", ...}

Both invocations use identical CLI flags, identical stdin content, identical env. The only difference is the parent process type (Node vs shell).

Expected behavior

The Bash tool's subprocess should be spawned with the same cwd as the one passed to the host's spawn() call (which is the same cwd that the system/init message correctly reports).

Actual behavior

The Bash tool's subprocess is spawned with cwd=/. Any pwd, relative ls, or cat ./file behaves as if the agent's workspace is the filesystem root.

Impact

Agent SDK hosts that run the CLI as a programmatic subprocess (typical for chatbots, automations, Claude Agent SDK integrations) can't rely on cwd for their bash tool operations. Workarounds require either:

  • Injecting a "your logical working directory is X" notice into systemPrompt.append and training the agent to use absolute paths or cd X && ...
  • Rewriting bash commands via a PreToolUse hook (fragile, has side effects)

Neither is a proper fix; both are user-space patches for what looks like an internal chdir inside the CLI.

Reproduced with multiple spawners

The same failure reproduces with:

  • child_process.spawn from Node
  • child_process.spawnSync({ input }) from Node
  • Bun.spawn({ cwd })
  • Python subprocess.run(..., cwd=target)
  • Node spawning /bin/sh -c "cd target && echo ... | claude ..." (the cd inside the shell has no effect on the final cwd)
  • detached: true spawn
  • All stdio combinations (pipe/inherit/ignore in any slot)

The failure does NOT reproduce when the shell itself is the direct parent of claude (interactive shell, nohup bash -c, etc.).

Things that did not fix it

Tested but ineffective from the host side:

  • CLAUDECODE=1 env var
  • CLAUDE_CODE_ENTRYPOINT=cli env var
  • CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1 env var
  • Shell-impersonation env vars (SHLVL, BASH_VERSION, PPID=<parent>)
  • --add-dir <target> flag
  • --bare flag
  • process.chdir(target) in Node before spawn (so parent cwd already equals target)
  • Passing cwd explicitly vs inheriting from parent

Key observation

The system/init message (stream-json output) always contains the correct cwd, so the CLI itself knows where it is. The failure is specifically in the Bash tool's subprocess spawn path. From binary string inspection, the tool uses cwd: YY_() where YY_() resolves to asyncLocalStorage.cwd ?? R_.cwd. Something between init and the first Bash call sets that to / when the parent is not a shell.

Asks

  1. Confirm whether this is intentional sandbox behavior or an actual bug.
  2. If intentional: document it and provide an escape hatch (env var or option).
  3. If a bug: fix Bash tool spawn to use the same cwd that system/init reports.

Happy to provide additional diagnostics (strace, DEBUG_CLAUDE_AGENT_SDK=1 output, etc.) on request.

extent analysis

TL;DR

The issue can be addressed by modifying the claude CLI to properly handle the working directory when spawned from a non-shell parent process.

Guidance

  • Investigate the asyncLocalStorage.cwd and R_.cwd values in the Bash tool's subprocess spawn path to understand why they resolve to / when the parent is not a shell.
  • Consider adding an environment variable or option to the claude CLI to allow for explicit specification of the working directory.
  • Review the claude CLI's initialization code to ensure that it correctly sets the working directory for the Bash tool's subprocess.
  • Test the claude CLI with different parent processes and working directories to ensure that the issue is resolved.

Example

No code example is provided as the issue requires investigation of the claude CLI's internal implementation.

Notes

The issue appears to be specific to the claude CLI and its interaction with non-shell parent processes. Further diagnostics, such as strace output or DEBUG_CLAUDE_AGENT_SDK=1 output, may be necessary to fully understand the issue.

Recommendation

Apply a workaround, such as injecting a notice into systemPrompt.append or rewriting bash commands via a PreToolUse hook, until the claude CLI is modified to properly handle the working directory.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The Bash tool's subprocess should be spawned with the same cwd as the one passed to the host's spawn() call (which is the same cwd that the system/init message correctly reports).

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING