{"record":{"id":"1272d9cbbed1ff08","repo":"can1357/oh-my-pi","slug":"daemon-operation-name-stdin-is-unavailable","errorCode":null,"errorMessage":"Daemon ${operation.name} stdin is unavailable","messagePattern":"Daemon (.+?) stdin is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/broker.ts","lineNumber":1124,"sourceCode":"\t\tconst timedOut = operation.for === \"ready\" && !pattern ? !readyObserved() : !woke;\n\t\treturn { op: \"wait\", daemon: record.snapshot, matched, timedOut };\n\t}\n\n\tasync #send(operation: Extract<DaemonOperation, { op: \"send\" }>): Promise<DaemonRpcResult> {\n\t\tconst record = this.#record(operation.name);\n\t\tawait this.#refreshDetached(record);\n\t\tif (terminalState(record.snapshot.state) || record.snapshot.state === \"stopping\") {\n\t\t\tthrow new Error(`Daemon ${operation.name} is ${record.snapshot.state}`);\n\t\t}\n\t\tif (operation.data === undefined && operation.signal === undefined) {\n\t\t\tthrow new Error(\"send requires data or signal\");\n\t\t}\n\t\tif (operation.data !== undefined) {\n\t\t\tif (record.pty) record.pty.write(operation.data);\n\t\t\telse if (record.input) {\n\t\t\t\trecord.input.write(operation.data);\n\t\t\t\tawait record.input.flush();\n\t\t\t} else throw new Error(`Daemon ${operation.name} stdin is unavailable`);\n\t\t}\n\t\tif (operation.signal) {\n\t\t\tif (process.platform === \"win32\" && record.pty) {\n\t\t\t\tif (operation.signal === \"SIGINT\") record.pty.write(\"\\u0003\");\n\t\t\t\telse record.pty.kill();\n\t\t\t} else {\n\t\t\t\tconst processRef = record.snapshot.pid === undefined ? null : Process.fromPid(record.snapshot.pid);\n\t\t\t\tif (!processRef) throw new Error(`Daemon ${operation.name} process is unavailable`);\n\t\t\t\tprocessRef.killTree(SIGNAL_NUMBER[operation.signal]);\n\t\t\t}\n\t\t}\n\t\treturn { op: \"send\", daemon: record.snapshot };\n\t}\n\n\tasync #stopRecord(record: ManagedDaemon, timeoutMs: number): Promise<void> {\n\t\tawait this.#refreshDetached(record);\n\t\tif (terminalState(record.snapshot.state)) return;\n\t\trecord.stopRequested = true;","sourceCodeStart":1106,"sourceCodeEnd":1142,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/broker.ts#L1106-L1142","documentation":"When a send operation includes data, the broker writes it to the daemon's PTY if one exists, otherwise to its captured stdin stream. If the daemon was started without a PTY and without stdin capture (record.pty and record.input are both unset), there is no writable input channel and the broker throws this error rather than silently dropping the data.","triggerScenarios":"Sending data to a daemon launched without pty: true and with stdin not piped/captured — the record has no input handle, so writes are impossible.","commonSituations":"Starting a detached daemon that doesn't allocate a PTY or stdin pipe, then later trying to feed it interactive input; changing launch flags (dropping pty) while clients still assume interactive stdin.","solutions":["Start the daemon with a PTY (or stdin capture) enabled so input can be delivered.","Restart the daemon with the correct input configuration, then re-send.","If the daemon doesn't need input, remove the send-data call instead of relying on stdin."],"exampleFix":"// before\nawait broker.start({ name: \"repl\", cwd, command: \"node\", args: [\"repl.js\"] }); // no pty/stdin\nawait broker.send({ name: \"repl\", data: \"1+1\\n\" }); // throws\n// after\nawait broker.start({ name: \"repl\", cwd, command: \"node\", args: [\"repl.js\"], pty: true });\nawait broker.send({ name: \"repl\", data: \"1+1\\n\" });","handlingStrategy":"validation","validationCode":"// At launch time, ensure an input channel exists:\nconst spec = { name, cwd, command, args, pty: true }; // or configure stdin capture\n// Before sending:\nconst snap = await broker.get(name);\nif (snap && !snap.hasPty && !snap.hasStdin) throw new Error(`${name} has no input channel`);","typeGuard":null,"tryCatchPattern":"try {\n  await broker.send({ name, data });\n} catch (err) {\n  if (err instanceof Error && err.message.endsWith(\"stdin is unavailable\")) {\n    // restart daemon with pty/stdin enabled, then resend\n  } else throw err;\n}","preventionTips":["Always launch interactive daemons with pty: true (or stdin capture) in the spec.","Keep launch configuration and send expectations in one place so dropping pty breaks visibly at startup.","Document which daemons accept input; treat input-less daemons as fire-and-forget."],"tags":["daemon","stdin","configuration"],"backgroundTag":"stdin-unavailable","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}