{"record":{"id":"a97ae3609b66d396","repo":"stablyai/orca","slug":"daemon-pty-id-is-awaiting-recovery","errorCode":null,"errorMessage":"Daemon PTY \"${id}\" is awaiting recovery","messagePattern":"Daemon PTY \"(.+?)\" is awaiting recovery","errorType":"exception","errorClass":"PtyWriteUnavailableError","httpStatus":null,"severity":"warning","filePath":"src/main/daemon/daemon-pty-adapter.ts","lineNumber":999,"sourceCode":"      return null\n    }\n  }\n\n  write(id: string, data: string): void {\n    this.markSessionDirty(id)\n    // Why recoverable and not just active: rejecting a write asks the pane to remount,\n    // which only helps if this endpoint can come back. A legacy adapter has no respawn,\n    // so its reattach fails and the pane rebuilds empty — losing scrollback the user\n    // could still read. Keep the pre-existing silent drop for those.\n    const recoverable =\n      this.activeSessionIds.has(id) && !this.respawnAdoptionClosed && Boolean(this.respawnFn)\n    if (\n      recoverable &&\n      (this.sessionsAwaitingDaemonRecovery.has(id) || !this.client.isConnected())\n    ) {\n      this.sessionsAwaitingDaemonRecovery.add(id)\n      this.reconnectAfterWriteFailure()\n      throw new PtyWriteUnavailableError(`Daemon PTY \"${id}\" is awaiting recovery`)\n    }\n    const delivered = this.client.notify('write', { sessionId: id, data })\n    if (!delivered && recoverable) {\n      this.sessionsAwaitingDaemonRecovery.add(id)\n      this.reconnectAfterWriteFailure()\n      throw new PtyWriteUnavailableError(`Daemon PTY \"${id}\" is awaiting recovery`)\n    }\n  }\n\n  resize(id: string, cols: number, rows: number): void {\n    this.markSessionDirty(id)\n    this.client.notify('resize', { sessionId: id, cols, rows })\n  }\n\n  pauseProducer(id: string): void {\n    if (!this.supportsProducerFlowControl) {\n      return\n    }","sourceCodeStart":981,"sourceCodeEnd":1017,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/daemon/daemon-pty-adapter.ts#L981-L1017","documentation":"DaemonPtyAdapter.write rejects synchronously with PtyWriteUnavailableError when the session is 'recoverable' (active, respawn possible, adoption not closed) AND either the session is already flagged as awaiting recovery or the daemon client is currently disconnected. The intent is to drive the renderer pane to remount on a connection that can actually come back, rather than silently dropping keystrokes.","triggerScenarios":"User typing into a terminal whose daemon connection just dropped; a second write arriving on a session already in sessionsAwaitingDaemonRecovery; client.notify path not yet re-established after a transient disconnect while respawnFn is configured.","commonSituations":"Daemon restart, network blip to a remote daemon, machine sleep/resume dropping the socket, slow reconnect after a daemon crash where the user kept typing.","solutions":["Catch PtyWriteUnavailableError at the pane layer and remount/retry once the adapter reports reconnection (sessionsAwaitingDaemonRecovery clears).","Do not infinitely retry in a tight loop — let reconnectAfterWriteFailure drive reconnection and re-issue on the recovered signal.","If this fires repeatedly, inspect the daemon client connection state (isConnected) and the respawn reason."],"exampleFix":"// before\ntry { adapter.write(sessionId, data) } catch { /* swallow */ }\n\n// after\nimport { isPtyWriteUnavailableError } from '../providers/pty-write-unavailable-error'\ntry {\n  adapter.write(sessionId, data)\n} catch (e) {\n  if (isPtyWriteUnavailableError(e)) {\n    // queue input, await reconnect, then drain the queue\n  } else throw e\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"import { isPtyWriteUnavailableError } from '../providers/pty-write-unavailable-error'\n\n// narrows unknown -> PtyWriteUnavailableError\nfunction isRecoverablePtyWrite(e: unknown): boolean {\n  return isPtyWriteUnavailableError(e)\n}","tryCatchPattern":"import { isPtyWriteUnavailableError } from '../providers/pty-write-unavailable-error'\n\ntry {\n  adapter.write(sessionId, data)\n} catch (e) {\n  if (isPtyWriteUnavailableError(e)) {\n    // queue input, await reconnect (sessionsAwaitingDaemonRecovery clears), then drain\n  } else {\n    throw e\n  }\n}","preventionTips":["Always use isPtyWriteUnavailableError to discriminate — never instanceof on a possibly-different bundle.\n","Back-pressure user input while a session awaits recovery to avoid throwing on every keystroke.","Reconnect on this signal via the adapter's reconnectAfterWriteFailure path; do not spin a tight retry."],"tags":["daemon","pty","write","recovery","reconnect","transient"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}