Yeachan-Heo/oh-my-codex · error · Error

Cannot use out-of-scope artifact path;

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.

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.

Appendix: 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)