CLAUDE_ENV_FILE reaches the shell, not the next hook
#3806 · measured on Claude Code 2.1.251, claude -p, --settings hooks, loopback Messages API, zero spend · 2026-08-30
The claim, and the measurement
src/hooks/README.md said:
Hook appends: export ORK_DEBUG=1
↓
CC reads the file after hook exits
↓
All subsequent hooks see
process.env.ORK_DEBUG === '1' <- never measured
a SessionStart hook wrote one line, four observers looked:
| observer | sees PROBE_EXPORTED |
|---|---|
| SessionStart hook (the writer) | n/a |
| PostToolUse hook (Bash) | unset |
| Stop hook | unset |
Bash tool command echo ${PROBE_EXPORTED} | yes |
Same result for a plugin hook (async), for a sub-hook inside the sync
sync-session-dispatcher, and with the runner invoked directly with
CLAUDE_ENV_FILE set (the file was written correctly every time). The export is a
Bash-facing channel. Hook-to-hook state goes through files.
Who is reading? Pick a reader
Consumer audit, decided per consumer
| site | was it broken? | what changed |
|---|---|---|
config-change/settings-reload writes export ORK_DEBUG=1 on /debug | no | it also wrote ~/.claude/logs/ork/debug-mode.flag, which lib/env.ts getLogLevel() already read as step 4. The comments called the dead channel "primary" and the live one "fallback"; they now name the reader each channel serves. The env append is kept: a shell command Claude runs does see $ORK_DEBUG. |
lib/env.ts resolution order comment | no | step 3 no longer claims the export arrives; step 4 is the working hook path, not a fallback |
prompt/cache-break-detector header: "persisted via CLAUDE_ENV_FILE" | no | the code has always used a turn-state file; the comment now says so |
lifecycle/session-env-setup | no | writes JSON state files, never an export; it was only the probe vehicle |
every other ORK_* variable a hook reads (31 names) | no | operator-set in the shell or settings env, never a session export |
lib/sandbox-posture.ts (#3322) | would have been | designed on the claim, measured dead before shipping, replaced by a direct settings read. That is how #3806 was found. |
No code path changed in this PR. The defect was a documented contract nobody had measured, and the one consumer survived it by having two channels. Whether hook-side blindness is a CC bug or the feature's intended scope is unsettled upstream; the observer table above is the repro either way.