Yeachan-Heo/oh-my-codex · error · Error
Cannot use out-of-scope artifact path; ${evidenceDescription
Error message
Cannot use out-of-scope artifact path; ${evidenceDescription} must be canonical. What it means
The worker pane is provably live but its process PID differs from the pinned expectedPanePid recorded when the worker was launched. A different PID means the original worker process died and something respawned or replaced it — sending work to it would target an unknown process, so the library throws.
Source
Thrown at src/autopilot/completion-gate.ts:92
&& !isAbsolute(allowedRel)
&& allowedRel !== '..'
&& !allowedRel.startsWith('..' + '/')
&& allowedPrefixes.some((prefix) => allowedRel.startsWith(prefix))
&& !isAbsolute(canonicalAllowedRel)
&& canonicalAllowedRel !== '..'
&& !canonicalAllowedRel.startsWith('../')
&& allowedPrefixes.some((prefix) => canonicalAllowedRel.startsWith(prefix));
}
function assertCanonicalArtifactPath(
state: JsonObject,
rawPath: unknown,
allowedPrefixes: readonly string[],
evidenceDescription: string,
): void {
const path = nonEmptyString(rawPath);
if (path && !isCanonicalArtifactPath(state, path, allowedPrefixes)) {
throw new Error(`Cannot use out-of-scope artifact path; ${evidenceDescription} must be canonical.`);
}
}
function hasAnyStringField(value: JsonObject, keys: string[]): boolean {
return keys.some((key) => nonEmptyString(value[key]).length > 0);
}
function stringField(value: JsonObject, key: string): string {
return nonEmptyString(value[key]);
}
function isImplementationPhase(phase: AutopilotChildPhase | null): boolean {
return phase === 'ultragoal' || phase === 'rework' || phase === 'team' || phase === 'ralph';
}
const ALLOWED_ACTIVE_TRANSITIONS: Readonly<Record<AutopilotChildPhase, readonly AutopilotChildPhase[]>> = {
'deep-interview': ['deep-interview', 'ralplan'],
ralplan: ['ralplan', 'ultragoal'],View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Fix the root cause of worker process exit if crashes are unintended
- Disable respawn automation for worker panes
- Re-launch the worker (fresh split + fresh PID) and update the pinned PID instead of reusing the old one
- If the respawn is expected, re-capture the PID via readExactWorkerPaneLivenessProofSync and re-pin
Example fix
# before tmux respawn-pane -k -t %4 # worker PID changes → later ops throw # after # relaunch through the library API so the pinned PID is refreshed
Defensive patterns
Strategy: fallback
Validate before calling
const proof = readExactWorkerPaneLivenessProofSync(workerPaneId); if (proof.status === 'live' && proof.pid !== pinnedPid) planRelaunchOrRepin();
Try / catch
catch (err) { if (/worker pane PID changed/.test(err.message)) { relaunchWorker(); } } Prevention
- Disable respawn-pane automation for workers
- Fix worker crashes at the source
- Re-pin PID only via a deliberate relaunch flow
When it happens
Trigger: Worker-pane operations after the worker process exited and the pane respawned (respawn-pane, remain-on-exit auto-respawn), or expectedPanePid captured from a previous pane incarnation.
Common situations: Flaky worker processes that crash and get respawned by automation, respawn hooks in tmux.conf, or persisted PIDs reused after session restart.
Related errors
- ${label} must not be a symlink: ${path}
- Refusing to overwrite existing ${repoRelative(cwd, missionPa
- auth path is not a directory: ${dir}
- Cannot advance Autopilot from ralplan to ultragoal with forg
- worker_notify_failed
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/26ed4613a5ef16ad.
Report an issue: GitHub.