stablyai/orca · critical · DaemonEndpointUnavailableError
Daemon endpoint unavailable: ${reason}
Error message
Daemon endpoint unavailable: ${reason} What it means
publishAndArm runs publishDaemonEndpoint, the link/probe/rename protocol that takes the canonical socket path. If the outcome is anything other than 'published' — i.e. 'occupied' (a live daemon owns it), 'lost' (we were replaced), or 'inconclusive' (timeout/EPERM that proves nothing) — the daemon throws DaemonEndpointUnavailableError(reason) and never serves. This is the single point the design declines rather than risking two daemons on one endpoint.
Source
Thrown at src/main/daemon/daemon-server.ts:320
// Serving from here, so later server errors are logged, not treated as startup failure.
startupSettled = true
resolve()
}, abandonStartup)
})
})
}
/**
* Takes the canonical endpoint, then makes this listener adoptable — in that order. Never rolled
* back: an aborting daemon just closes, and the next publisher replaces the dead entry.
*/
private async publishAndArm(bindPath: string): Promise<void> {
const outcome = await publishDaemonEndpoint(bindPath, this.socketPath, probeSocketConnect)
if (outcome.status !== 'published') {
// The only point the design declines to serve, so a field regression surfaces here.
this.log.log('endpoint-publish-declined', { reason: outcome.status })
console.warn(`[daemon] Endpoint unavailable at startup: reason=${outcome.status}`)
throw new DaemonEndpointUnavailableError(outcome.status)
}
this.ownedSocketIdentity = outcome.identity
let publishedOwnership = false
try {
// The PID/nonce record must exist before the token makes this listener adoptable.
this.publishEndpointOwnership()
publishedOwnership = true
writeFileSync(this.tokenPath, this.token, { mode: 0o600 })
} catch (error) {
// Roll back only a record we wrote; anything else at that path belongs to another daemon.
if (publishedOwnership && this.pidPath && this.launchNonce) {
unlinkOwnedDaemonPidFile(this.pidPath, process.pid, this.launchNonce)
}
this.ownedSocketIdentity = null
throw error
}
if (this.protocolVersion >= CLEAN_DISCONNECT_PROTOCOL_VERSION) {
// A parent crash before the first client pair must not strand an empty daemon forever.View on GitHub (pinned to 1136503c6a)
Solutions
- On reason === 'occupied': do not fork a second daemon — adopt the existing one (see daemon-entry.ts which maps this to DAEMON_EXIT_ENDPOINT_OCCUPIED).
- On 'lost' or 'inconclusive': retry startup once; if it persists, inspect the socket directory perms and any third-party process holding the path.
- Ensure no sweeper deletes the canonical path (per AGENTS.md: 'Do not add a sweeper'); let the publisher replace dead entries atomically.
- Confirm the socket directory is writable and not on an exotic filesystem (network FS) that breaks POSIX link/rename guarantees.
Defensive patterns
Strategy: try-catch
Type guard
import { DaemonEndpointUnavailableError } from './daemon-endpoint-ownership'
function isEndpointUnavailable(e: unknown): e is DaemonEndpointUnavailableError {
return e instanceof DaemonEndpointUnavailableError
}
// discriminated by reason: 'occupied' | 'lost' | 'inconclusive' Try / catch
try {
await daemon.start()
} catch (e) {
if (isEndpointUnavailable(e)) {
if (e.reason === 'occupied') {
// adopt the existing daemon; do NOT fork a second one
process.exit(DAEMON_EXIT_ENDPOINT_OCCUPIED)
}
// 'lost' / 'inconclusive' — bounded retry of startup
} else throw e
} Prevention
- Map reason==='occupied' to adoption, never to a parallel fork (see daemon-entry.ts).
- Never add a sweeper that removes the canonical path (per AGENTS.md).
- Confirm the socket directory is writable and on a POSIX-correct filesystem.
When it happens
Trigger: Launching a daemon while another live daemon already owns the endpoint (occupied); a race where another publisher renamed over our entry between probe and publish (lost); a connect timeout or EPERM during the liveness probe (inconclusive).
Common situations: Two Orca instances starting at once; a stale daemon still alive when a new one launches; restrictive filesystem perms (EPERM) on the socket directory; running on a filesystem where link/rename semantics trip the protocol.
Related errors
- Daemon no longer owns its endpoint; reconnect
- [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/ee1936b7204e0e8a.
Report an issue: GitHub.