{"record":{"id":"8fd65dda4fe9db57","repo":"moeru-ai/airi","slug":"send-doesn-t-work-without-stdin-input-claude-co","errorCode":null,"errorMessage":"`send` doesn't work without stdin input, Claude Code hooks events are expected to be piped to this command.","messagePattern":"`send` doesn't work without 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":60,"sourceCode":"  .action(async (_, flags: Options) => {\n    if (flags?.quiet) {\n      logger = logger.withLogLevel(-1 as LogLevel)\n    }\n    else {\n      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> {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/plugins/airi-plugin-claude-code/src/cli.ts#L42-L78","documentation":"Thrown by the `send` CLI action (cli.ts:60) when process.stdin.isTTY is true, i.e. the command was run in an interactive terminal rather than having a Claude Code hook event piped to its stdin. The send command exists solely to receive the JSON hook payload that Claude Code pipes to configured hook commands, so an interactive invocation has no data to read and is rejected up front before readStdin() blocks forever.","triggerScenarios":"Running `airi-plugin-claude-code send` (or the package's bin) directly from a shell prompt; invoking the binary in a CI step that allocates a TTY but does not pipe a payload; calling the command from a script that forgot to echo/pipe the hook JSON into it.","commonSituations":"A developer runs the binary manually to smoke-test the hook wiring; the hook entry in Claude Code settings points to the wrong command so Claude Code never pipes anything; a wrapper script launches the binary in a TTY-allocating shell.","solutions":["Pipe a Claude Code hook event JSON into the command: `echo '{\"hook_event_name\":\"UserPromptSubmit\",\"prompt\":\"hi\"}' | airi-plugin-claude-code send`.","In real use, do not invoke `send` by hand — register it as a Claude Code hook command so Claude Code pipes the hook payload to stdin automatically.","If you need to test the binary in a TTY context, feed it via stdin redirection (`< payload.json`) or `printf '...' |` so stdin.isTTY is false.","Double-check the Claude Code hook configuration references this binary as the command, not a parent shell that swallows stdin."],"exampleFix":"# before: interactive run, stdin.isTTY === true\n$ airi-plugin-claude-code send\n# -> `send` doesn't work without stdin input...\n\n# after: pipe the hook payload\n$ echo '{\"hook_event_name\":\"UserPromptSubmit\",\"prompt\":\"hello\"}' | airi-plugin-claude-code send\n# or: airi-plugin-claude-code send < hook-event.json","handlingStrategy":"validation","validationCode":"// Guard the CLI action: only attempt readStdin when stdin is actually piped.\nif (stdin.isTTY) {\n  process.stderr.write('error: `send` requires piped hook JSON. Usage: ... | airi-plugin-claude-code send\\n')\n  exit(2)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call `send` interactively — register it as a Claude Code hook command so Claude Code pipes the event JSON.","When smoke-testing, always pipe a payload (`echo '{...}' | ...`) or redirect a file (`< payload.json`) so stdin.isTTY is false.","Document the expected invocation in the plugin README so hook wiring always pipes stdin."],"tags":["cli","stdin","claude-code","hooks","configuration","plugin"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}