moeru-ai/airi · error · Error
`send` received empty stdin input, Claude Code hooks events
Error message
`send` received empty stdin input, Claude Code hooks events are expected to be piped to this command.
What it means
Thrown by the `send` CLI action (cli.ts:65) when stdin is piped (not a TTY) but the full contents read by readStdin() are empty or whitespace-only. This catches the case where a hook is wired up (so stdin.isTTY is false) but Claude Code — or a wrapper — piped nothing meaningful, which would otherwise produce a confusing JSON parse error or a silent no-op.
Source
Thrown at plugins/airi-plugin-claude-code/src/cli.ts:65
logger = logger.withLogLevelString(flags?.logLevel ?? LogLevelString.Log)
}
async function readStdin(): Promise<string> {
const chunks: string[] = []
for await (const chunk of stdin) {
chunks.push((chunk as Buffer).toString('utf-8'))
}
return chunks.join('')
}
if (stdin.isTTY) {
throw new Error('`send` doesn\'t work without stdin input, Claude Code hooks events are expected to be piped to this command.')
}
const stdinInput = await readStdin()
if (!stdinInput.trim()) {
throw new Error('`send` received empty stdin input, Claude Code hooks events are expected to be piped to this command.')
}
const hookEvent = JSON.parse(stdinInput) as HookInput
if (hookEvent.hook_event_name === 'UserPromptSubmit') {
const channelServer = new Client({ name: 'proj-airi:plugin-claude-code', autoConnect: false })
await channelServer.connect()
channelServer.send({ type: 'input:text', data: { text: hookEvent.prompt } })
}
})
export async function runCLI(): Promise<void> {
cli.parse(argv, { run: false })
if (cli.options.debug) {
let namespace: string
if (cli.options.debug === true) {View on GitHub (pinned to 27111382b4)
Solutions
- Verify the producer of stdin actually emits a non-empty JSON payload — log `echo $PAYLOAD | wc -c` or the wrapper's stdout before piping.
- Confirm the hook event name (`hook_event_name`) is one Claude Code emits (e.g. UserPromptSubmit) and that the Claude Code version supports piping JSON for that event.
- If testing manually, ensure the piped string is a real JSON object, not just a newline: `printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"hi"}' | airi-plugin-claude-code send`.
- Check any wrapper script in the Claude Code hook config for a missing `cat`/redirect or an early `exit` that discards the payload.
Example fix
# before: wrapper emits nothing
$ '' | airi-plugin-claude-code send
# -> `send` received empty stdin input...
# after: pipe a non-empty JSON hook event
$ printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"hello"}' | airi-plugin-claude-code send Defensive patterns
Strategy: validation
Validate before calling
// After reading stdin, fail fast with a precise message on empty/whitespace input.
const stdinInput = await readStdin()
if (stdinInput.trim().length === 0) {
process.stderr.write('error: `send` received empty stdin. Confirm the hook pipes a JSON event.\n')
exit(2)
} Prevention
- Validate the hook producer emits non-empty JSON for the event you registered for — not all Claude Code hook events carry a JSON payload.
- Log the wrapper script's stdout length before piping into `send` to catch a silently-failed upstream stage.
- Use `printf '%s' '{...}'` (not `echo` with an empty string) when feeding test payloads.
When it happens
Trigger: A Claude Code hook command is configured but the hook fires with an empty stdin (e.g. wrong hook type, or Claude Code version that does not emit JSON for that event); a wrapper script pipes the output of a command that produced no stdout; a user manually runs `echo '' | airi-plugin-claude-code send`; a piping bug drops the payload before the binary reads it.
Common situations: The hook is registered for an event that Claude Code does not actually populate with JSON; a shell wrapper redirects the wrong file/command into the binary; an upstream pipe stage failed silently and emitted no bytes; trailing newline-only input from a malformed producer.
Related errors
- `send` doesn't work without stdin input, Claude Code hooks e
- cap-vite [vite args...] -- <ios|android> [cap run args...]
- Missing value for `${optionName}`.
- Unsupported capture format "${requestedFormat}". Expected "p
- Unsupported route path "${routePath}". Route paths must star
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/7d12682b3b318087.
Report an issue: GitHub.