{"record":{"id":"ee1936b7204e0e8a","repo":"stablyai/orca","slug":"daemon-endpoint-unavailable-reason","errorCode":null,"errorMessage":"Daemon endpoint unavailable: ${reason}","messagePattern":"Daemon endpoint unavailable: (.+?)","errorType":"exception","errorClass":"DaemonEndpointUnavailableError","httpStatus":null,"severity":"critical","filePath":"src/main/daemon/daemon-server.ts","lineNumber":320,"sourceCode":"          // Serving from here, so later server errors are logged, not treated as startup failure.\n          startupSettled = true\n          resolve()\n        }, abandonStartup)\n      })\n    })\n  }\n\n  /**\n   * Takes the canonical endpoint, then makes this listener adoptable — in that order. Never rolled\n   * back: an aborting daemon just closes, and the next publisher replaces the dead entry.\n   */\n  private async publishAndArm(bindPath: string): Promise<void> {\n    const outcome = await publishDaemonEndpoint(bindPath, this.socketPath, probeSocketConnect)\n    if (outcome.status !== 'published') {\n      // The only point the design declines to serve, so a field regression surfaces here.\n      this.log.log('endpoint-publish-declined', { reason: outcome.status })\n      console.warn(`[daemon] Endpoint unavailable at startup: reason=${outcome.status}`)\n      throw new DaemonEndpointUnavailableError(outcome.status)\n    }\n    this.ownedSocketIdentity = outcome.identity\n    let publishedOwnership = false\n    try {\n      // The PID/nonce record must exist before the token makes this listener adoptable.\n      this.publishEndpointOwnership()\n      publishedOwnership = true\n      writeFileSync(this.tokenPath, this.token, { mode: 0o600 })\n    } catch (error) {\n      // Roll back only a record we wrote; anything else at that path belongs to another daemon.\n      if (publishedOwnership && this.pidPath && this.launchNonce) {\n        unlinkOwnedDaemonPidFile(this.pidPath, process.pid, this.launchNonce)\n      }\n      this.ownedSocketIdentity = null\n      throw error\n    }\n    if (this.protocolVersion >= CLEAN_DISCONNECT_PROTOCOL_VERSION) {\n      // A parent crash before the first client pair must not strand an empty daemon forever.","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/daemon/daemon-server.ts#L302-L338","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"import { DaemonEndpointUnavailableError } from './daemon-endpoint-ownership'\n\nfunction isEndpointUnavailable(e: unknown): e is DaemonEndpointUnavailableError {\n  return e instanceof DaemonEndpointUnavailableError\n}\n\n// discriminated by reason: 'occupied' | 'lost' | 'inconclusive'","tryCatchPattern":"try {\n  await daemon.start()\n} catch (e) {\n  if (isEndpointUnavailable(e)) {\n    if (e.reason === 'occupied') {\n      // adopt the existing daemon; do NOT fork a second one\n      process.exit(DAEMON_EXIT_ENDPOINT_OCCUPIED)\n    }\n    // 'lost' / 'inconclusive' — bounded retry of startup\n  } else throw e\n}","preventionTips":["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."],"tags":["daemon","endpoint","ownership","startup","posix","critical"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}