Bug: Async hooks receive stdin JSON without trailing newline, breaking bash `read`
Resolved 💬 2 comments Opened Mar 3, 2026 by EliasSchlie Closed Apr 1, 2026
Description
Async hooks ("async": true) receive JSON on stdin without a trailing newline, while synchronous hooks include one. This causes bash's read builtin to return exit code 1 (EOF without delimiter), silently discarding valid input when used idiomatically:
# Common pattern — silently loses data for async hooks
if read -t 1 -r line; then
echo "$line" # never reached
fi
Root Cause
In the hook execution code, the sync path writes:
stdin.write(data + "\n");
But the async path writes:
stdin.write(data); // no newline
stdin.end();
Impact
Any async hook that reads stdin with bash read gets empty data. This affects all fields passed via stdin (session_id, transcript_path, cwd, etc.).
Workaround: read -t 1 -r line || true — ignore the exit code and check the variable instead.
Reproduction
- Create an async hook that logs stdin:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "bash -c 'read -r line; echo \"exit=$? line=$line\" >> /tmp/hook-stdin-test.log'",
"async": true
}]
}]
}
}
- Trigger a Stop event
- Check
/tmp/hook-stdin-test.log— showsexit=1 line=(empty)
Expected
Async and sync hooks should receive identical stdin format (with trailing newline).
Environment
- Claude Code 2.1.63
- macOS (bash 3.2 and bash 5.x both affected)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗