Skip to content

fix(hooks): read hook input from stdin JSON instead of environment variables - #4

Open
sergiobuilds wants to merge 1 commit into
LilMGenius:ralphthonfrom
sergiobuilds:fix/hook-input-contract
Open

fix(hooks): read hook input from stdin JSON instead of environment variables#4
sergiobuilds wants to merge 1 commit into
LilMGenius:ralphthonfrom
sergiobuilds:fix/hook-input-contract

Conversation

@sergiobuilds

Copy link
Copy Markdown

pre-tool-use.sh and post-tool-use.sh read TOOL_NAME, FILE_PATH, and TOOL_OUTPUT from the environment. Claude Code delivers hook input as a JSON object on stdin and sets none of those, so both conditions test an empty string. The guards are installed, registered in hooks.json, and unreachable.

The PLOON-overwrite guard is the one that matters — it is the thing standing between an agent and a clobbered interview log, which is the one file in this project that cannot be regenerated.

Measured

Same input, two contracts:

$ echo '{"tool_name":"Write","tool_input":{"file_path":"personas/testuser/persona.md"}}' \
    | bash hooks/pre-tool-use.sh
(no output)

$ TOOL_NAME=Write FILE_PATH=personas/testuser/persona.md bash hooks/pre-tool-use.sh
WARNING: Writing to persona file personas/testuser/persona.md

session-start.sh is unaffected — it reads files, not the environment, and works today.

Second defect, found on the way

The path test was $FILE_PATH == personas/*, anchored at the start. Hooks receive an absolute path, so that pattern would still never match even after the input was fixed. Widened to *personas/*.

After

input expected actual
stdin JSON, Write to a persona file warns warns
stdin JSON, absolute persona path warns warns
stdin JSON, Read silent silent
stdin JSON, Write to README.md silent silent
post-tool-use, slop in tool_response warns warns
post-tool-use, string tool_response warns warns
malformed stdin exit 0, silent exit 0, silent

Parsing goes through node, which the project already requires, so no jq dependency is added. Malformed input falls through to empty rather than aborting under set -euo pipefail, so a hook can never take a session down with it.

…riables

- `pre-tool-use` and `post-tool-use` read `TOOL_NAME`, `FILE_PATH`, and `TOOL_OUTPUT` from the environment, which Claude Code never sets, so both guards were installed, listed in hooks.json, and unreachable — every condition evaluated against an empty string; they now parse the stdin JSON object for `tool_name`, `tool_input.file_path`, and `tool_response`, and fall through silently on malformed input
- the persona-path test widened from `personas/*` to `*personas/*` because the hook receives an absolute path, which the anchored pattern could never match
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant