{"record":{"id":"b8d3b0fd7f078232","repo":"can1357/oh-my-pi","slug":"a-detached-daemon-cannot-allocate-a-pty","errorCode":null,"errorMessage":"A detached daemon cannot allocate a PTY","messagePattern":"A detached daemon cannot allocate a PTY","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/broker.ts","lineNumber":601,"sourceCode":"\t\t\t}\n\t\t\tcase \"restart\":\n\t\t\t\treturn this.#restart(operation.name);\n\t\t\tcase \"describe\": {\n\t\t\t\tconst record = this.#record(operation.name);\n\t\t\t\tawait this.#refreshDetached(record);\n\t\t\t\treturn { op: \"describe\", daemon: record.snapshot, spec: record.spec };\n\t\t\t}\n\t\t\tcase \"shutdown\":\n\t\t\t\treturn { op: \"shutdown\" };\n\t\t}\n\t}\n\n\tasync #start(spec: DaemonSpec, owner?: string): Promise<DaemonRpcResult> {\n\t\tif (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,47}$/.test(spec.name)) {\n\t\t\tthrow new Error(\"Daemon name must be 1-48 letters, numbers, dots, underscores, or hyphens\");\n\t\t}\n\t\tif (spec.detached && spec.pty) {\n\t\t\tthrow new Error(\"A detached daemon cannot allocate a PTY\");\n\t\t}\n\t\tif (\n\t\t\tspec.pty &&\n\t\t\tprocess.platform === \"win32\" &&\n\t\t\t[\".bat\", \".cmd\"].includes(path.extname(spec.application).toLowerCase())\n\t\t) {\n\t\t\tthrow new Error('Windows batch files require application \"cmd.exe\" with the batch path after \"/c\"');\n\t\t}\n\t\tif (this.#startingNames.has(spec.name)) {\n\t\t\tthrow new Error(`Daemon ${spec.name} is already starting`);\n\t\t}\n\t\tthis.#startingNames.add(spec.name);\n\t\tlet record: ManagedDaemon;\n\t\ttry {\n\t\t\tconst existing = this.#records.get(spec.name);\n\t\t\tif (existing) await this.#refreshDetached(existing);\n\t\t\tif (existing && !terminalState(existing.snapshot.state)) {\n\t\t\t\tthrow new Error(`Daemon ${spec.name} is already ${existing.snapshot.state}`);","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/broker.ts#L583-L619","documentation":"DaemonBroker.#start rejects combinations of spec.detached and spec.pty: a daemon started detached (surviving independently of the broker/session) cannot also be given a pseudo-terminal allocation, since the PTY lifecycle is tied to the broker process. The check runs after name validation and before any process is spawned.","triggerScenarios":"Submitting a DaemonSpec to the start RPC with both detached: true and pty: true — e.g. config that enables 'run in background' and 'interactive terminal' at once.","commonSituations":"Merging user-facing toggles ('detach', 'use PTY') that were assumed independent; copying a spec template from an interactive setup into a detached launcher; migrating an interactive dev-server daemon to a detached startup service without clearing pty.","solutions":["Set pty: false if the daemon must be detached","Set detached: false if the daemon genuinely needs a PTY","Split into two daemons: a detached non-PTY one plus an interactive PTY one, if both behaviors are required"],"exampleFix":"// before\nawait broker.start({ name: 'dev', detached: true, pty: true }); // throws\n// after\nawait broker.start({ name: 'dev', detached: true, pty: false });","handlingStrategy":"validation","validationCode":"function assertValidDaemonSpec(spec: DaemonSpec): void {\n  if (spec.detached && spec.pty) {\n    throw new Error('detached and pty are mutually exclusive');\n  }\n}\n// call before broker.start(spec)","typeGuard":null,"tryCatchPattern":"try {\n  await broker.start(spec);\n} catch (err) {\n  if (err instanceof Error && err.message === 'A detached daemon cannot allocate a PTY') {\n    return broker.start({ ...spec, pty: false });\n  }\n  throw err;\n}","preventionTips":["Model detach and PTY as one mutually exclusive 'execution mode' in config/UI instead of two independent booleans","Validate merged config objects after combining presets or templates","Document the constraint where the spec type is defined","Default pty to false for detached/background launch paths"],"tags":["configuration","daemon","pty","incompatible-options"],"backgroundTag":"incompatible-options-combination","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}