stablyai/orca · error · Error

Daemon no longer owns its endpoint; reconnect

Error message

Daemon no longer owns its endpoint; reconnect

What it means

DAEMON_ENDPOINT_LOST_MESSAGE: the daemon detected it no longer owns the canonical socket endpoint (hasLostEndpointOwnership() returned true), meaning a replacement daemon has published over its path. A non-attach createOrAttach is refused because any session created on a lost endpoint would be unreachable by clients, producing terminals that accept keystrokes but never run. Attach to an already-hosted session is intentionally still allowed so the retiring daemon can drain.

Source

Thrown at src/main/daemon/daemon-server.ts:988

          throw new Error('Daemon temporarily unavailable; reconnect')
        }
        if (!client?.authenticatedPairEstablished || client.streamSocket === null) {
          // Why: a control-only replacement can't own terminal admission or erase the prior client's retirement request.
          throw new Error('Daemon client connection is incomplete; reconnect')
        }
        const p = request.payload
        const attachOnly = p.attachOnly === true
        // Why check here and not only on the watchdog: publishing cannot be made atomic against
        // a publisher preempted between proving an entry dead and replacing it, so this daemon
        // can lose the endpoint at any moment. The watchdog notices within a poll, which is far
        // too late if a session was accepted in between — that session is then reachable by
        // nobody, and the user sees a terminal that acknowledges input and never runs it.
        // Why creation only: an attach reaches a session this daemon already hosts, over a
        // connection that already exists. Refusing that would break the drain a retiring daemon
        // depends on, and it strands nothing — the session is already here.
        if (!attachOnly && this.hasLostEndpointOwnership()) {
          this.requestRetirementForLostEndpoint()
          throw new Error(DAEMON_ENDPOINT_LOST_MESSAGE)
        }
        this.createOrAttachInFlight++
        let routedSessionId = p.sessionId
        let result: Awaited<ReturnType<TerminalHost['createOrAttach']>>
        try {
          if (
            p.agentSessionEnsure !== undefined &&
            (!isAgentSessionExecutionClaim(p.agentSessionEnsure.claim) ||
              !isAgentSessionSurfaceBinding(p.agentSessionEnsure.surface))
          ) {
            throw new Error('agent_session_identity_required')
          }
          if (!attachOnly) {
            await this.preparePtySpawnUnlessCanceled(p.sessionId, clientId)
          }
          if (p.historySeed !== undefined && p.historySeedTransferId !== undefined) {
            throw new Error('Multiple terminal history seed sources')
          }

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Treat this as a reconnect signal: the client should discover and connect to the replacement daemon that now owns the endpoint, then retry createOrAttach there.
  2. On the host side, let the retiring daemon finish draining its existing sessions (it allows attach-only) before forcing it down.
  3. Avoid launching concurrent daemons that bind the same canonical socket path; the ownership protocol is designed for sequential handover, not parallel publishers.
  4. If this fires repeatedly, check that the publish-endpoint ownership protocol (link/rename/verify) is not being bypassed by a custom launcher.

Example fix

// before: retry createOrAttach on the same (superseded) daemon
try {
  await daemon.rpc('createOrAttach', payload)
} catch (e) { /* ignore */ }

// after: on endpoint-lost, rediscover the live daemon first
try {
  await daemon.rpc('createOrAttach', payload)
} catch (e) {
  if (isDaemonGoneError(e)) {
    daemon = await discoverDaemonEndpoint()
    await daemon.rpc('createOrAttach', payload)
  }
}
Defensive patterns

Strategy: retry

Type guard

// Match the daemon client's detection of an endpoint-lost condition
function isDaemonEndpointLost(e: unknown): boolean {
  return e instanceof Error && e.message === 'Daemon no longer owns its endpoint; reconnect'
}

Try / catch

try {
  await daemon.rpc('createOrAttach', payload)
} catch (e) {
  if (isDaemonEndpointLost(e)) {
    daemon = await discoverAndConnectDaemon()
    await daemon.rpc('createOrAttach', payload)
  } else { throw e }
}

Prevention

When it happens

Trigger: A non-attach 'createOrAttach' (attachOnly !== true) issued to a daemon that has been superseded at its canonical socket path by a newer publisher. The replacement won the link/rename ownership protocol, so this daemon is retiring.

Common situations: Two daemon launches racing during app restart or version upgrade; a watchdog relaunching the daemon while the old one still hosts sessions; a developer manually starting a second daemon instance that takes over the endpoint.

Related errors


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