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

  1. 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).
  2. On 'lost' or 'inconclusive': retry startup once; if it persists, inspect the socket directory perms and any third-party process holding the path.
  3. Ensure no sweeper deletes the canonical path (per AGENTS.md: 'Do not add a sweeper'); let the publisher replace dead entries atomically.
  4. 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

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


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/ee1936b7204e0e8a. Report an issue: GitHub.