anthropics / anthropics/claude-code
[BUG] Claude Code on the Web: a repository's .claude/settings.json is silently ignored, so its hooks never run
- 主要言語
- Python
- スター
- 145k
- フォーク
- 23.1k
- PR マージ指標
- PR 指標を取得中
説明
### 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 `/.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](https://code.claude.com/docs/en/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](https://github.com/anthropics/claude-code/issues/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:
```json
{"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/
/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.:
```json
{
"hooks": {
"SessionStart": [
{ "hooks": [ { "type": "command", "command": "date > /tmp/hook-ran.log" } ] }
]
}
}
```
3. Start a new cloud session on that repository.
4. 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//.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](https://github.com/anthropics/claude-code/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](https://github.com/anthropics/claude-code/issues/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](https://github.com/anthropics/claude-code/issues/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 `/.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.
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
調査の方向性
Start by reproducing the two-attached-repository setup and inspect how the cloud session chooses its project root and loads .claude/settings.json. Compare the repository hook with the working registration in ~/.claude/settings.json, and check the project and hook diagnostics described in the issue. Done means repository settings are loaded, or a clear warning and supported registration path explains when they are skipped.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- json
- 領域
- cloud, devtools
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 活発
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 42/100