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

  1. 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
      }]
    }]
  }
}
  1. Trigger a Stop event
  2. Check /tmp/hook-stdin-test.log — shows exit=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)

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗