diff --git a/src/cli/index.ts b/src/cli/index.ts index 98a8fd8..457282d 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -31,7 +31,7 @@ import type { CodexPromptEnvelope, ContextLogEntry } from '../types/codex-context.js'; -import { RetryManager } from '../core/errors.js'; +import { RetryManager, DaemonConflictError } from '../core/errors.js'; import { HiveMindYamlFormatter } from '../utils/yaml-output.js'; import { parseFileContent, parseJsonInput, loadFileThroughFeedforward } from './feedforward.js'; import { InstructionParser } from '../instructions/index.js'; @@ -599,10 +599,15 @@ async function useSystem(description: string, fn: (system: CodexSynapticSystem) if (!alreadyRunning && process.env.CODEX_ALLOW_LOCAL_WITH_DAEMON !== '1') { const background = getBackgroundStatus(); if (background.running) { - throw new Error( + throw new DaemonConflictError( `Background daemon already running (pid ${background.pid}). ` + 'To avoid split-brain state, use `codex-synaptic background attach` for daemon-backed monitoring, ' + - '`codex-synaptic background stop` before local commands, or set CODEX_ALLOW_LOCAL_WITH_DAEMON=1 to override.' + '`codex-synaptic background stop` before local commands, or set CODEX_ALLOW_LOCAL_WITH_DAEMON=1 to override.', + { + daemonPid: background.pid, + alreadyRunning, + allowLocalWithDaemon: process.env.CODEX_ALLOW_LOCAL_WITH_DAEMON + } ); } } diff --git a/src/core/errors.ts b/src/core/errors.ts index 00700ad..2585f2e 100644 --- a/src/core/errors.ts +++ b/src/core/errors.ts @@ -35,7 +35,10 @@ export enum ErrorCode { // Bridge errors BRIDGE_ERROR = 'BRIDGE_ERROR', MCP_ERROR = 'MCP_ERROR', - A2A_ERROR = 'A2A_ERROR' + A2A_ERROR = 'A2A_ERROR', + + // CLI/Daemon errors + DAEMON_CONFLICT = 'DAEMON_CONFLICT' } export class CodexSynapticError extends Error { @@ -111,6 +114,16 @@ export class BridgeError extends CodexSynapticError { } } +/** + * Error thrown when a daemon conflict is detected (split-brain prevention) + */ +export class DaemonConflictError extends CodexSynapticError { + constructor(message: string, context?: Record) { + super(ErrorCode.DAEMON_CONFLICT, message, context, false); + this.name = 'DaemonConflictError'; + } +} + /** * Circuit breaker for handling failures gracefully */