Hook stderr output shows raw command path as prefix, leaking implementation details
Resolved 💬 4 comments Opened Mar 31, 2026 by iroha924 Closed May 4, 2026
Description
When a hook writes to stderr (e.g., exit 2 for DENY/block), Claude Code displays the message with the full command as a prefix:
Stop hook error: [node /path/to/hook.mjs stop]: Custom message here.
The [node /path/to/hook.mjs stop] prefix is an implementation detail that provides no value to users. For plugin hooks, it exposes internal paths like ${CLAUDE_PLUGIN_ROOT}/dist/hook.mjs.
Expected behavior
Show only the hook's stderr message, optionally with a human-readable hook identifier:
Stop hook: Custom message here.
Or use the plugin name if available:
my-plugin (Stop): Custom message here.
Reproduction
- Create a minimal hook that exits with code 2:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "echo 'Please fix errors before finishing.' >&2 && exit 2",
"timeout": 5
}
]
}
]
}
}
- Start a session and let Claude attempt to stop
- Observe the output:
Stop hook error: [echo 'Please fix errors before finishing.' >&2 && exit 2]: Please fix errors before finishing.
The full command is shown as a prefix, and the intentional block is labeled "error".
Additional context
- The word "error" is misleading for intentional blocking behavior (exit 2). Related: #12671, #17088
- When both plugin hooks and settings hooks define hooks for the same event, the message appears multiple times with different command paths, making it look like separate errors
- Plugin authors have no control over this formatting
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗