FileChanged hook stops firing after any cwd change in the session; CwdChanged watchPaths does not restore it (2.1.270)
Environment
- Claude Code CLI 2.1.270, run by the Claude desktop app 1.52386.6 (macOS, Darwin 25.5.0),
--input-format stream-json, project settings in<cwd>/.claude/settings.json. - Hook config (documented shape, hooks.md §FileChanged):
``json`
{"hooks":{"FileChanged":[{"matcher":"test.bell","hooks":[{"type":"command","command":"/Users/me/doorbell-test/ring.sh","asyncRewake":true}]}]}}
ring.sh` logs each invocation, prints new lines to stderr and exits 2 when the file grew (else 0).
Expected (docs)
- hooks.md §FileChanged: runs "no matter what changed the file … a process outside Claude Code entirely"; matcher segments are "literal filename in the working directory".
- hooks.md async constraints: "an
asyncRewakehook that exits with code 2 wakes Claude immediately even when the session is idle." - hooks.md §CwdChanged output:
watchPaths"Replaces the current dynamic watch list. Paths from yourmatcherconfiguration are always watched."
Observed
| Step | Action (external process appends one line to test.bell with O_APPEND) | Hook ran? | Session woke? |
|---|---|---|---|
| A | session idle, fresh | yes, +1 s | yes, +4 s |
| B | session mid sleep 120 | yes, same second | yes, 6 s after the turn ended |
| D | session first ran one Bash command cd /tmp && ls >/dev/null && pwd, then idle | no | no (120 s) |
| E | session then ran cd <original cwd> && pwd, idle | no | no |
| F0 | fresh session with an added CwdChanged hook that returns {"watchPaths":["<abs path to test.bell>"]} | yes | yes |
| F | same session ran cd /tmp && …; the CwdChanged hook ran and returned the absolute path | no | no |
After any directory change in the main conversation, the FileChanged watcher never fires again for the session — not for the matcher path (contradicting "always watched"), and not for a watchPaths re-pin from a CwdChanged hook. The session never receives any notice that the watch was lost.
Minimal repro
mkdir ~/fc && cd ~/fc && mkdir .claude && touch bell; settings above with matcherbelland a hookecho hit >> ~/fc/log; exit 2withasyncRewake: true.- Start a session in
~/fc; from another terminalecho x >> ~/fc/bell→ hook runs, session wakes. - In the session, run Bash
cd /tmp && pwd. From the other terminalecho y >> ~/fc/bell→ nothing, ever.
Impact
A FileChanged+asyncRewake doorbell is the only documented way for an outside process to wake an idle interactive session. Any cd inside a Bash command silently disarms it, so real sessions (which cd within their first minute) never see it work, and conclude the feature is broken. We lost ~12 h of teammate messages on 2026-09-18 to this before measuring it.
Asks
- Keep matcher paths watched across
CwdChanged(as documented), or re-resolve them against the new cwd and say so. - Honour
watchPathsreturned by aCwdChangedhook after the change. - Emit a visible notice when the watch list becomes empty/dead.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗