[BUG] Claude Code on the Web: a repository's .claude/settings.json is silently ignored, so its hooks never run
What's Wrong?
In a Claude Code on the Web cloud session, the session's project root is the parent directory of the repository checkout, not the checkout itself. Claude Code therefore never loads <repo>/.claude/settings.json, and any hooks registered there never fire. Nothing warns — not the session, not the logs.
This contradicts the documented contract in Configure cloud environments, which:
- lists "Your repo's
.claude/settings.jsonhooks" as carrying over to a cloud session — "Yes — part of the clone" - tells users to "add a SessionStart hook to your repo's
.claude/settings.json" for dependency installation - states that "
$CLAUDE_PROJECT_DIRresolves to the repository root, so the hook finds the script regardless of the session's working directory" — while in our sessions$CLAUDE_PROJECT_DIRwas empty
Likely trigger (hypothesis, untested): our environment attaches two repositories. The documentation describes the single-repository case, where the project root is the checkout. With two attached repositories the project root appears to become their common parent, /home/user. A single-repository session may well behave as documented; we did not test one.
Impact. Repositories that use a SessionStart hook to finish environment setup (install a toolchain, hydrate Git LFS, initialize submodules) start every cloud session with that work undone, then fail at the first build or test — minutes into the conversation, far from the cause.
Why it is hard to diagnose from inside a session:
- A silently ignored settings file looks identical to a hook that ran and did nothing.
- The launcher's own
SessionStarthook (session-start-git-identity.sh, from/root/.claude/launcher-settings.json) supplies a decoy signal in the diagnostic log with a plausible exit code and duration, so the failure reads as "the repo's hook ran and exited cleanly." - Hook stdout never reaches the session's context (#10373), so a hook made deliberately loud is still invisible to the agent.
CLAUDE.mdis loaded from the checkout, including its@-imports — so file discovery reaches the subdirectory while settings loading does not, which actively suggests the repo is being treated as the project.
It took four cloud test-session rounds to locate this; two intermediate rounds produced fixes for a misdiagnosis, because every in-session signal pointed at the hook's own logic rather than at registration.
What Should Happen?
Either .claude/settings.json is loaded from each attached repository's checkout, or Claude Code says it is skipping it. A silent skip of a version-controlled configuration file is the core problem; the hook is only the most visible casualty.
Suggested fixes, in order of preference:
- Load
.claude/settings.jsonfrom each attached repository's checkout. - If that is not intended, emit a warning naming the path that was skipped and the project root that was used.
- Document the project-root behaviour for multi-repository cloud sessions, and provide a supported way to register a
SessionStarthook that belongs to a repository.
Error Messages/Logs
There is no error. That is the defect. The only SessionStart evidence in the diagnostic log belongs to the launcher's own hook:
{"timestamp":"2026-08-24T11:42:09.499Z","level":"info","event":"hook_spawn_started","data":{"hook_event_name":"SessionStart","index":0}}
{"timestamp":"2026-08-24T11:42:09.508Z","level":"info","event":"hook_spawn_completed","data":{"hook_event_name":"SessionStart","index":0,"duration_ms":15,"exit_code":0,"aborted":false}}
Environment observations from the session:
$ ls /root/.claude/projects/ # exactly one project: the parent, not the checkout
-home-user
$ ls /home/user/.claude/ # does not exist
ls: cannot access '/home/user/.claude/': No such file or directory
$ echo "[${CLAUDE_PROJECT_DIR:-}]" # empty in the hook process
[]
$ echo "$CLAUDE_CODE_REMOTE"
true
$ echo "$PWD" ; echo "$HOME"
/home/user/<repo>
/root
The repo's own hook never appears in any log, and its script never executes (verified by it writing a file on every branch, including its early-exit branch — the file is never created).
Steps to Reproduce
- Create a cloud environment with two attached repositories.
- In one repository, commit
.claude/settings.jsonregistering aSessionStarthook that proves it ran, e.g.:
{
"hooks": {
"SessionStart": [
{ "hooks": [ { "type": "command", "command": "date > /tmp/hook-ran.log" } ] }
]
}
}
- Start a new cloud session on that repository.
- Check
/tmp/hook-ran.log— it does not exist. Checkls /root/.claude/projects/, which shows a single project entry for/home/user, the parent of the checkout.
The hook is never registered because /home/user/<repo>/.claude/settings.json is one level below the project root. A hook with an explicit "matcher": "startup|resume" behaves the same; we later registered the identical hook entry into ~/.claude/settings.json and /home/user/.claude/settings.json from the environment's setup script, and it then fired on every session — so the hook definition itself was always valid.
Related issues
- #33815 (closed):
$CLAUDE_PROJECT_DIRempty/unset in hook execution. We observe the same emptiness in cloud sessions, which is why the workaround below cannot rely on it. - #10373 (open):
SessionStarthook stdout never processed for brand-new conversations. Compounds this bug — a hook made deliberately loud is still invisible to the session. We have added a cloud-session confirmation there rather than duplicating it here. - #19134 (closed as stale): documents the same repo-level
SessionStarthook pattern for cloud dependency management — the pattern this bug silently breaks.
Workaround, for anyone else hitting this
Have the environment's setup script (which runs at snapshot-build time, as root) merge the hook registration into the settings a session actually reads — $HOME/.claude/settings.json and <project-root>/.claude/settings.json — preserving any existing content so the launcher's own hooks survive. Confirmed working: the hook then fires on every session.
Claude Code Version
Unknown — Claude Code on the Web cloud session, observed 2026-08-24. The cloud environment provisions its own Claude Code build; no version string is surfaced in the session.