Yeachan-Heo/oh-my-codex · error · Error
Autopilot handoff workingDirectory does not match the…
Error message
Autopilot handoff workingDirectory does not match the selected workspace.
What it means
Thrown by assertBoundHandoffIdentity() when the handoff JSON contains a workingDirectory string that differs from the current working directory (workspace) of the autopilot command. It prevents applying a handoff bound to one workspace to another workspace.
Solutions
- cd into the exact working directory recorded in the handoff's workingDirectory field before running advance
- Remove the workingDirectory field (or make it non-string) from the handoff so the current cwd is stamped in
- Normalize both paths (resolve symlinks) if they point to the same directory but differ textually
Example fix
// before cd /other/dir && omx autopilot advance --handoff-json h.json // h.json has "workingDirectory": "/repo" // after cd /repo && omx autopilot advance --handoff-json h.json
Defensive patterns
Strategy: validation
Validate before calling
const h = JSON.parse(fs.readFileSync(handoffPath,'utf-8'));
if (h.workingDirectory && path.resolve(h.workingDirectory) !== path.resolve(process.cwd())) {
process.chdir(path.resolve(h.workingDirectory));
} Try / catch
try { await advance(handoff); } catch (e) { if (e.message.includes('workingDirectory does not match')) { process.chdir(recordedDir); await advance(handoff); } else throw e; } Prevention
- Run autopilot commands from a canonical (symlink-free) repo path
- Strip workingDirectory from handoff files meant to be portable
When it happens
Trigger: Calling autopilot advance from a directory different from the one where the handoff was produced, while the handoff JSON has a string workingDirectory field. Non-string/absent workingDirectory passes and is overwritten with cwd.
Common situations: Running the CLI from a symlinked or differently-spelled path, moving the repo directory between producing and consuming the handoff, or running the command from the repo root while the handoff was recorded in a subdirectory.
Related errors
- Autopilot handoff session_id does not match the selected…
- --handoff-json must resolve to a JSON object.
- autopilot.workingDirectory must match the selected writable…
- Missing --handoff-json.
- Missing --task.
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/d38b18806cfd14c9.
Report an issue: GitHub.
Appendix: source
Thrown at src/cli/autopilot.ts:60
if (valueFlags.has(args[i])) { i += 1; continue; }
if (!args[i].startsWith('--')) words.push(args[i]);
}
return words.join(' ').trim();
}
async function jsonInput(raw: string): Promise<Record<string, unknown>> {
const text = raw.trim().startsWith('{') ? raw : await readFile(raw, 'utf-8');
const parsed = JSON.parse(text) as unknown;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('--handoff-json must resolve to a JSON object.');
return parsed as Record<string, unknown>;
}
function assertBoundHandoffIdentity(handoff: Record<string, unknown>, cwd: string, sessionId?: string): void {
if (typeof handoff.session_id === 'string' && handoff.session_id !== sessionId) {
throw new Error('Autopilot handoff session_id does not match the selected session.');
}
if (typeof handoff.workingDirectory === 'string' && handoff.workingDirectory !== cwd) {
throw new Error('Autopilot handoff workingDirectory does not match the selected workspace.');
}
handoff.session_id = sessionId;
handoff.workingDirectory = cwd;
}
async function readAutopilot(cwd: string, sessionId?: string) {
return sessionId
? readModeStateForExplicitSession('autopilot', sessionId, cwd)
: readModeState('autopilot', cwd);
}
/**
* A terminalization that carried skipped gates must never read as clean success. The durable
* marker is the machine token `complete-with-skipped-gates`; this renders the human string and
* names each skipped gate with its missing evidence so the report is actionable.
*/
function skippedGateReport(state: Record<string, unknown>): string | null {
if (state.completion_status !== 'complete-with-skipped-gates') return null;View on GitHub (pinned to 3ad79a8a6f)