anthropics / anthropics/claude-code

FileChanged hook stops firing after any cwd change in the session; CwdChanged watchPaths does not restore it (2.1.270)

Open
#95,440 0 comments 0 reactions 0 assignees View on GitHub
area:hooks bug has repro platform:macos
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

## 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 `/.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 `asyncRewake` hook 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 your `matcher` configuration 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 && pwd`, idle | no | no |
| F0 | fresh session with an added `CwdChanged` hook that returns `{"watchPaths":[""]}` | 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
1. `mkdir ~/fc && cd ~/fc && mkdir .claude && touch bell` ; settings above with matcher `bell` and a hook `echo hit >> ~/fc/log; exit 2` with `asyncRewake: true`.
2. Start a session in `~/fc`; from another terminal `echo x >> ~/fc/bell` → hook runs, session wakes.
3. In the session, run Bash `cd /tmp && pwd`. From the other terminal `echo 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
1. Keep matcher paths watched across `CwdChanged` (as documented), or re-resolve them against the new cwd and say so.
2. Honour `watchPaths` returned by a `CwdChanged` hook after the change.
3. Emit a visible notice when the watch list becomes empty/dead.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with hooks.md §FileChanged and §CwdChanged, then run the minimal repro using the documented matcher and CwdChanged watchPaths. Trace the watcher lifecycle across the Bash cd and verify the matcher and returned absolute path remain effective afterward; done means external appends still invoke the hook and asyncRewake wakes the session.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, shell
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.