[BUG] Claude Code on the Web: a repository's .claude/settings.json is silently ignored, so its hooks never run

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 24, 2026

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.json hooks" 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_DIR resolves to the repository root, so the hook finds the script regardless of the session's working directory" — while in our sessions $CLAUDE_PROJECT_DIR was 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 SessionStart hook (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.md is 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:

  1. Load .claude/settings.json from each attached repository's checkout.
  2. If that is not intended, emit a warning naming the path that was skipped and the project root that was used.
  3. Document the project-root behaviour for multi-repository cloud sessions, and provide a supported way to register a SessionStart hook 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

  1. Create a cloud environment with two attached repositories.
  2. In one repository, commit .claude/settings.json registering a SessionStart hook that proves it ran, e.g.:
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "date > /tmp/hook-ran.log" } ] }
    ]
  }
}
  1. Start a new cloud session on that repository.
  2. Check /tmp/hook-ran.log — it does not exist. Check ls /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_DIR empty/unset in hook execution. We observe the same emptiness in cloud sessions, which is why the workaround below cannot rely on it.
  • #10373 (open): SessionStart hook 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 SessionStart hook 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.

View original on GitHub ↗