diff --git a/src/adapters/cli/codex-app.ts b/src/adapters/cli/codex-app.ts index 61e4499b..30472164 100644 --- a/src/adapters/cli/codex-app.ts +++ b/src/adapters/cli/codex-app.ts @@ -27,7 +27,9 @@ export function createCodexAppAdapter(pathOverride?: string): CliAdapter { let cachedCodexBin: string | undefined; return { id: 'codex-app', - authPaths: ['~/.codex/auth.json'], + // Whole ~/.codex kept REAL (see codex.ts): its SQLite state/log DBs can't get + // fcntl locks on the sandbox home overlay, so codex hangs ~57s then exits 1. + authPaths: ['~/.codex'], resolvedBin: process.execPath, // resolvedBin is node-running-the-runner; the REAL codex is spawned later for diff --git a/src/adapters/cli/codex.ts b/src/adapters/cli/codex.ts index 6cf7b40f..3907f748 100644 --- a/src/adapters/cli/codex.ts +++ b/src/adapters/cli/codex.ts @@ -125,7 +125,13 @@ export function createCodexAdapter(pathOverride?: string): CliAdapter { let cachedBin: string | undefined; return { id: 'codex', - authPaths: ['~/.codex/auth.json'], + // Whole ~/.codex kept REAL, not just auth.json: codex opens SQLite state/log + // DBs there (state_*.sqlite / logs_*.sqlite). Under the file sandbox the home + // is an overlayfs merge, and overlayfs (kernel + fuse) doesn't support the + // POSIX fcntl locks SQLite needs — the connection pool blocks ~57s then codex + // exits 1 ("pool timed out"). Binding the dir real gives working locks and + // keeps login/history persistent (same rationale as auth.json). + authPaths: ['~/.codex'], get resolvedBin(): string { return (cachedBin ??= resolveCommand(rawBin)); }, buildArgs({ sessionId, resume, resumeSessionId, workingDir, model, disableCliBypass }) { diff --git a/test/cli-adapters.test.ts b/test/cli-adapters.test.ts index 61581b80..60d9676f 100644 --- a/test/cli-adapters.test.ts +++ b/test/cli-adapters.test.ts @@ -308,9 +308,12 @@ describe('codex buildArgs', () => { expect(args.join('\n')).not.toContain('BOTMUX_TURN_ID'); }); - it('keeps Codex home untouched', () => { + it('keeps the whole ~/.codex real in the sandbox (SQLite needs fcntl locks the home overlay lacks)', () => { expect(adapter.buildSpawnEnv).toBeUndefined(); - expect(adapter.authPaths).toEqual(['~/.codex/auth.json']); + // Not just auth.json: codex's state_*.sqlite / logs_*.sqlite live under + // ~/.codex and time out (~57s → exit 1) if the dir is on the overlayfs home, + // which doesn't support POSIX byte-range locks. Bind the whole dir real. + expect(adapter.authPaths).toEqual(['~/.codex']); // skillsDir resolves under CODEX_HOME (default ~/.codex) so it tracks where // Codex actually scans skills when CODEX_HOME is overridden. expect(adapter.skillsDir).toBe(join(codexHome(), 'skills'));