[FEATURE] Allow a hook to change the session working directory (programmatic /cd)
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
A hook can react to a working-directory change today via the CwdChanged event — it receives old_cwd/new_cwd and can re-sync the environment (the direnv pattern). But a hook has no deterministic way to instigate a change of working directory via hook, or programmatic path for automation to relocate a running session.
The existing escape hatches don't cover this:
CLAUDE_ENV_FILEonly sets environment variables for Bash subprocesses. It does not move the session's primary working directory, re-anchor Glob/Grep defaults, or change which.claude/config is loaded.additionalContextinjection is LLM-visible text — it can suggest, but not deterministically perform, a relocation, and the model has no reliable tool to do so anyway.cdinside a single Bash tool call only affects that one command.- The
SlashCommand/Skilltool is model-invoked and doesn't expose/cd.
So /cd now exists for a human to type, but there is no equivalent an automation layer can trigger.
Proposed Solution
Add an optional setCwd field to hook JSON output (under hookSpecificOutput) that instructs Claude Code to relocate the session — performing the same relocation /cd does (workspace current_dir, Glob/Grep anchor, .claude/ config reload), then emitting the existing CwdChanged event so reactive hooks run unchanged.
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"setCwd": "/Users/me/projects/other-repo"
}
}
- Supported on
UserPromptSubmit,SessionStart,PostToolUseandStopso automation can re-anchor after a unit of work completes. - Invalid/nonexistent path: ignore the field and surface a warning (loud failure preferred over a silent no-op), consistent with how other invalid hook output is handled.
- If multiple hooks return
setCwdin one cycle, let last writer win.
This is deliberately scoped to CWD only. I can imagine a use for a more general _"hook emits an action"_ pattern, but purpose-built fields like setCwd are much smaller in scope and require much less research to establish their necessary scope. We can submit separate proposals for other actions if needs arise.
Alternative Solutions
CLAUDE_ENV_FILE— sets Bash subprocess env only; cannot relocate the session. Doesn't solve it.- Prose instruction via
additionalContext— non-deterministic; relies on the model choosing to act and having a tool that performs a durable relocation, which it doesn't. - Exit and relaunch in the new directory — works, but loses conversation context and session state; the cost this request exists to avoid.
cdin a Bash call — resets between calls, so it affects a single command only and does not address the need the way that/cdcurrently does.
Priority
Medium - Would be very helpful
Feature Category
Developer tools/SDK
Use Case Example
- I am writing tooling to coordinate parallel Claude Code sessions across git worktrees.
- When the user selects a new item to work on, my tooling creates a new worktree; if Claude does not change its internal
CWDthen it can potentially write to and thus make the Git main branch dirty. - If a hook could returns
setCwdpointing at the correct worktree then Claude Code could re-anchor its session deterministically as a gate — Glob/Grep,.claude/config — and fireCwdChangedso environment hooks re-sync. - This would allow my tooling to ensure with a hard guarantee that the Claude session to always working in the correct directory and with no relaunch and no lost context instead of depending on the model interpreting a prose guideline.
Additional Context
/cd shipped recently (#3473) for interactive/user-facing relocation, and the reactive half of the loop — CwdChanged — already exists for hooks. This request closes the loop by adding the instigating half: a way for a hook to trigger the relocation that /cd already performs.
There is a large existing cluster of requests for the user/model-facing version (#3473, #1628, #19903, #21636, #38308, #36937, #50302, #60151) but none ask for the hook-driven variant. That is what this issue contributes.