stablyai/orca · error · TerminalHostGoneError
terminal_host_gone
Error message
terminal_host_gone
What it means
Thrown as a TerminalHostGoneError from attachStablePaneOwner when reattaching to a stable terminal pane whose host daemon socket endpoint is provably gone. isDaemonEndpointGoneError only matches connect-syscall errors with code ENOENT or ECONNREFUSED — i.e. the daemon's published socket path no longer exists or actively refused the connection. The translation happens here because the paired-runtime RPC layer downstream strips the underlying socket error's `code` and `syscall` fields, so the signal would be lost if not converted now.
Source
Thrown at src/main/ipc/pty.ts:859
expectedIncarnationId: owner.runtimeIncarnationId ?? owner.persistedIncarnationId,
expectedIncarnationIsAuthoritative: owner.runtimeIncarnationId !== undefined,
isNewSession: undefined,
command: undefined,
commandDelivery: undefined,
startupCommandDelivery: undefined,
launchAgent: undefined,
startupIngress: undefined,
agentSessionEnsure: undefined,
agentSessionCreateOperationId: undefined,
onPtySpawnCommitted: undefined
})
} catch (error) {
if (error instanceof TerminalSessionOwnerUnverifiedError) {
throw new Error('terminal_pane_owner_unverified')
}
// Why: translate before paired-runtime RPC strips the socket error's code and syscall.
if (isDaemonEndpointGoneError(error)) {
throw new TerminalHostGoneError()
}
if (!isPtyAlreadyGoneError(error)) {
throw error
}
const ownerBeforeRetire = args.resolveOwner?.()
if (
ownerBeforeRetire &&
(ownerBeforeRetire.ptyId !== owner.ptyId ||
ownerBeforeRetire.runtimeIncarnationId !== owner.runtimeIncarnationId ||
ownerBeforeRetire.hasPersistedBinding !== owner.hasPersistedBinding ||
ownerBeforeRetire.persistedIncarnationId !== owner.persistedIncarnationId)
) {
throw new Error('terminal_pane_owner_changed')
}
runtime?.onPtyExit(owner.ptyId, 0, owner.incarnationId)
clearProviderPtyState(owner.ptyId)
ptyOwnership.delete(owner.ptyId)
if (View on GitHub (pinned to 1136503c6a)
Solutions
- Catch TerminalHostGoneError by class and surface a 'terminal needs to be reopened' UI; the pane cannot reattach to a dead host.
- Verify daemon liveness before attempting attach (provider.listProcesses) so the gone-endpoint state is detected explicitly instead of via throw.
- Restart the daemon (or trigger a fresh spawn that rebinds the canonical endpoint) and let the pane spawn fresh rather than reattach.
- If recurring, inspect daemon logs for why it exited and check the canonical socket path is being republished by the endpoint-ownership protocol.
Example fix
// before
try {
await attachStablePaneOwner(args)
} catch (err) {
// generic handling — loses the host-gone signal
}
// after
import { TerminalHostGoneError } from '../daemon/daemon-errors'
try {
await attachStablePaneOwner(args)
} catch (err) {
if (err instanceof TerminalHostGoneError) {
// pane cannot be reattached; retire the persisted binding and spawn fresh
retirePersistedStablePaneOwner(args.store, args.owner, args.worktreeId, args.connectionId)
return spawnForStablePane({ ...args, owner: null })
}
throw err
} Defensive patterns
Strategy: try-catch
Validate before calling
import { isDaemonEndpointGoneError } from '../daemon/daemon-errors'
import type { IPtyProvider } from '../providers/types'
async function canReachDaemon(provider: IPtyProvider, deadlineMs = 1000): Promise<boolean> {
try {
await provider.listProcesses({ deadlineMs })
return true
} catch (err) {
return !isDaemonEndpointGoneError(err)
}
} Type guard
import { TerminalHostGoneError } from '../daemon/daemon-errors'
function isTerminalHostGone(err: unknown): err is TerminalHostGoneError {
return err instanceof TerminalHostGoneError
} Try / catch
try {
await attachStablePaneOwner(args)
} catch (err) {
if (err instanceof TerminalHostGoneError) {
retirePersistedStablePaneOwner(args.store, args.owner, args.worktreeId, args.connectionId)
return spawnForStablePane({ ...args, owner: null })
}
throw err
} Prevention
- Before reattach, call provider.listProcesses with a short deadline to confirm the daemon endpoint is reachable.
- Wrap begin/end of any daemon-replacement flow in try/finally so the socket always ends in a known state.
- Never treat a missing socket file as proof the daemon is dead — connect ECONNREFUSED/ENOENT is the only proof per the endpoint-ownership protocol.
When it happens
Trigger: Calling provider.spawn with attachOnly:true on a stable pane owner after the daemon process exited, was upgraded, or its canonical socket path was renamed away. Reproduced when a worktree's terminal pane tries to reattach on app restart or window re-creation but the daemon that hosted the original PTY is no longer bound.
Common situations: Daemon crash or restart between sessions; daemon upgrade mid-flight; socket path replaced by a fresh publisher (per the daemon-endpoint-ownership protocol); SSH host disconnected after the pane owner was persisted; user killed the daemon process manually.
Related errors
- terminal_pane_owner_unverified
- [plain-node-entry-guard] "${entryName}" reaches chunk "${chu
- [plain-node-entry-guard] could not smoke-load daemon-entry.j
- [plain-node-entry-guard] daemon-entry.js did not exit within
- [plain-node-entry-guard] daemon-entry.js was killed by ${res
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/82f4fffe498282d3.
Report an issue: GitHub.