How to write a Claude Code hook that cannot steer your agent A developer building ccdeck, a local dashboard for Claude Code and Codex sessions, published the design of a read-only Claude Code hook that is structurally prevented from steering the agent. The hook never writes to stdout, exits 0 on every path including uncaught exceptions, declares no onFailure or async options, and sends only one outbound request, with a test file that statically bans console output and any non-zero exit code in the source. The author notes a prior bug where a malformed registry record crashed the hook with exit 1, surfacing a hook error on every tool call. I build ccdeck https://github.com/BarganConstantin/ccdeck , a local dashboard for Claude Code and Codex sessions, and its hook is the worked example; everything about it is quoted from the source at the v3.42.0 tag https://github.com/BarganConstantin/ccdeck/tree/v3.42.0 hook/hook.js , src/server/installer.mjs and src/web/ tests /hook-read-only.test.ts , with two more tests named where they come up ; at 3.48.0 the hook, its tests and the quoted installer lines are unchanged. Claude Code's behaviour is quoted from Anthropic's hooks reference https://code.claude.com/docs/en/hooks as I read it on 14 October 2026. Plenty of hooks only want to watch: a logger, a notifier, a dashboard. They still run on every event they register for, and on some events Claude Code waits for them and reads what they say. A PreToolUse hook sees rm -rf before it runs, and its answer can decide whether it does. The read-only claim needs to hold in the code. List every channel the hook has for talking back, close each one, and test that it stays closed. A command hook gets the event as JSON on stdin. From the reference, it has these ways to affect what happens next: