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

  1. Fix the root cause of worker process exit if crashes are unintended
  2. Disable respawn automation for worker panes
  3. Re-launch the worker (fresh split + fresh PID) and update the pinned PID instead of reusing the old one
  4. 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

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


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/26ed4613a5ef16ad. Report an issue: GitHub.