jared hebb ~ %

Claude Code hooks

The FileChanged hook in Claude Code

FileChanged fires when a watched file changes on disk. Its matcher uses a narrower syntax than every other event.

This event only exists for files a SessionStart hook put on the watch list in the first place, so a FileChanged hook that never fires is almost always missing that other half, not broken on its own.

When it fires
When a file on the watch list changes on disk, whoever changed it.
Can it block?
No.
Matcher
Yes, on literal filenames
Matcher examples
.envrc|.env package.json

What it receives

The event arrives as JSON on standard input. These are the fields worth reading.

FieldTypeWhat it is
file_pathstringThe file that changed.
session_idstringStable for the session.
cwdstringWorking directory.
transcript_pathstringPath to the transcript.

Exit codes

The exit code is the decision. Anything the script writes to standard error on a blocking exit is what Claude gets told.

ExitWhat happens
0Success.
2No blocking behavior.
otherNon-blocking.

What it can return

Nothing returned changes anything. Pair this with the watchPaths field a SessionStart hook can return, which is how files get onto the watch list in the first place.

No decision fields.

Noticing when dependencies move under a running session

A long session holds a picture of the project from when it started. If something changes package.json underneath it, the session does not find out until something breaks.

In settings.json

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": "package.json",
        "hooks": [
          { "type": "command", "command": "node ~/.claude/hooks/deps-changed.mjs" }
        ]
      }
    ]
  }
}

The script

const input = JSON.parse(require('fs').readFileSync(0, 'utf8')) || {};
console.log(`${input.file_path} changed on disk. ` +
  'Re-read it before relying on what you loaded earlier.');
process.exit(0);

Use a SessionStart hook returning watchPaths to decide which files raise this event.

The catch

The matcher on this event uses a narrower character set than the others: letters, digits, underscores and pipes only. A pattern that works elsewhere may not work here.