fix(claude): make Edit/Write hooks read tool data from stdin, not env#471
Merged
Merged
Conversation
The inline Edit|Write hooks branched on $CLAUDE_FILE_PATH / $CLAUDE_NEW_STRING / $CLAUDE_CONTENT, which Claude Code does not populate — it passes tool data as JSON on stdin and exposes only $CLAUDE_PROJECT_DIR. Every guard and formatter therefore silently no-op'd, and the PreToolUse guards used a non-blocking `exit 1`. Verified empirically on Claude Code 2.1.156: the runtime injects .tool_input.file_path on stdin and no $CLAUDE_FILE_PATH in the env. Changes (hooks only): - Read the path via `jq -r '.tool_input.file_path // empty'`. - Derive REL_PATH from $CLAUDE_PROJECT_DIR for in-container tools. - PreToolUse guards now block with `exit 2` (message to stderr). - addSql guard spans new_string/content/edits[].new_string so it catches Edit, Write and MultiEdit. - Widen every Edit|Write matcher to Edit|MultiEdit|Write. - Add phpstan-baseline.neon to the generated/locked deny glob. - PostToolUse formatters stay fail-open (`|| true`). Also add a SessionStart notice that warns (stderr, non-blocking exit 2) when `jq` or `intelephense` is missing on the host, since without jq the hooks fail open and silently no-op again. Install steps stay in CLAUDE.md (platform-neutral notice). CLAUDE.md: document jq as a host prerequisite and note the hooks fail open without it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The inline
Edit|Writehooks in.claude/settings.jsonbranched on$CLAUDE_FILE_PATH(and the addSql guard on$CLAUDE_NEW_STRING/$CLAUDE_CONTENT). Claude Code does not populate those env vars — it passes tool data as JSON on stdin (.tool_input.file_path, …) and exposes only$CLAUDE_PROJECT_DIR. As a result every guard and formatter silently no-op'd, and the PreToolUse guards used a non-blockingexit 1.Verified empirically (not assumed)
Probed the live runtime on Claude Code 2.1.156 (jq 1.7.1):
CLAUDE_FILE_PATHin env?CLAUDE_PROJECT_DIRin env?.tool_input.file_path?jq -r '.tool_input.file_path'extracts it?Then confirmed the old config was inert: a scratch migration with
$this->addSql(...)wrote unblocked, and a formatting deviation was not corrected by php-cs-fixer.Fix (hooks only)
jq -r '.tool_input.file_path // empty'.REL_PATHfrom$CLAUDE_PROJECT_DIRfor in-container tools.exit 2(message to stderr) instead of the non-blockingexit 1.new_string/content/edits[].new_string→ catchesEdit,Write, andMultiEdit.Edit|Writematcher toEdit|MultiEdit|Write.phpstan-baseline.neonto the generated/locked deny glob (closes drift with CLAUDE.md).|| true).Diff touches only the
hooksobject —permissions, plugins, and MCP config are byte-identical.SessionStart prerequisite notice
Added a
SessionStart(matcherstartup|resume|clear) check that warns whenjqorintelephenseis missing on the host — writes a single notice to stderr andexit 2(shown to the user, non-blocking). Withoutjqthe corrected hooks fail open and silently no-op again, so the notice makes that setup requirement visible. The notice is platform-neutral; install steps live in CLAUDE.md.Verification of the fix
exit 2forWrite/Edit/MultiEditpayloads; passes clean migrations and non-migration files.exit 2forcomposer.lock,phpstan-baseline.neon,generated-api.ts; passes ordinary source.$x=1 ;→$x = 1;(confirmsREL_PATH+ container workdir).Writeof a migration containing$this->addSqlwas rejected with:src/**.php) passed.jqmust be installed where Claude Code runs. Without it the corrected hooks fail open and silently no-op — which is exactly what the new SessionStart notice surfaces. CLAUDE.md now documents this.🤖 Generated with Claude Code