{"record":{"id":"7d12682b3b318087","repo":"moeru-ai/airi","slug":"send-received-empty-stdin-input-claude-code-hoo","errorCode":null,"errorMessage":"`send` received empty stdin input, Claude Code hooks events are expected to be piped to this command.","messagePattern":"`send` received empty stdin input, Claude Code hooks events are expected to be piped to this command\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/airi-plugin-claude-code/src/cli.ts","lineNumber":65,"sourceCode":"      logger = logger.withLogLevelString(flags?.logLevel ?? LogLevelString.Log)\n    }\n\n    async function readStdin(): Promise<string> {\n      const chunks: string[] = []\n      for await (const chunk of stdin) {\n        chunks.push((chunk as Buffer).toString('utf-8'))\n      }\n\n      return chunks.join('')\n    }\n\n    if (stdin.isTTY) {\n      throw new Error('`send` doesn\\'t work without stdin input, Claude Code hooks events are expected to be piped to this command.')\n    }\n\n    const stdinInput = await readStdin()\n    if (!stdinInput.trim()) {\n      throw new Error('`send` received empty stdin input, Claude Code hooks events are expected to be piped to this command.')\n    }\n\n    const hookEvent = JSON.parse(stdinInput) as HookInput\n\n    if (hookEvent.hook_event_name === 'UserPromptSubmit') {\n      const channelServer = new Client({ name: 'proj-airi:plugin-claude-code', autoConnect: false })\n      await channelServer.connect()\n\n      channelServer.send({ type: 'input:text', data: { text: hookEvent.prompt } })\n    }\n  })\n\nexport async function runCLI(): Promise<void> {\n  cli.parse(argv, { run: false })\n\n  if (cli.options.debug) {\n    let namespace: string\n    if (cli.options.debug === true) {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/plugins/airi-plugin-claude-code/src/cli.ts#L47-L83","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: wrapper emits nothing\n$ '' | airi-plugin-claude-code send\n# -> `send` received empty stdin input...\n\n# after: pipe a non-empty JSON hook event\n$ printf '%s' '{\"hook_event_name\":\"UserPromptSubmit\",\"prompt\":\"hello\"}' | airi-plugin-claude-code send","handlingStrategy":"validation","validationCode":"// After reading stdin, fail fast with a precise message on empty/whitespace input.\nconst stdinInput = await readStdin()\nif (stdinInput.trim().length === 0) {\n  process.stderr.write('error: `send` received empty stdin. Confirm the hook pipes a JSON event.\\n')\n  exit(2)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["cli","stdin","claude-code","hooks","validation","plugin"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}