{"record":{"id":"26ed4613a5ef16ad","repo":"Yeachan-Heo/oh-my-codex","slug":"cannot-use-out-of-scope-artifact-path-evidenced","errorCode":null,"errorMessage":"Cannot use out-of-scope artifact path; ${evidenceDescription} must be canonical.","messagePattern":"Cannot use out-of-scope artifact path; (.+?) must be canonical\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/autopilot/completion-gate.ts","lineNumber":92,"sourceCode":"    && !isAbsolute(allowedRel)\n    && allowedRel !== '..'\n    && !allowedRel.startsWith('..' + '/')\n    && allowedPrefixes.some((prefix) => allowedRel.startsWith(prefix))\n    && !isAbsolute(canonicalAllowedRel)\n    && canonicalAllowedRel !== '..'\n    && !canonicalAllowedRel.startsWith('../')\n    && allowedPrefixes.some((prefix) => canonicalAllowedRel.startsWith(prefix));\n}\n\nfunction assertCanonicalArtifactPath(\n  state: JsonObject,\n  rawPath: unknown,\n  allowedPrefixes: readonly string[],\n  evidenceDescription: string,\n): void {\n  const path = nonEmptyString(rawPath);\n  if (path && !isCanonicalArtifactPath(state, path, allowedPrefixes)) {\n    throw new Error(`Cannot use out-of-scope artifact path; ${evidenceDescription} must be canonical.`);\n  }\n}\n\nfunction hasAnyStringField(value: JsonObject, keys: string[]): boolean {\n  return keys.some((key) => nonEmptyString(value[key]).length > 0);\n}\n\nfunction stringField(value: JsonObject, key: string): string {\n  return nonEmptyString(value[key]);\n}\n\nfunction isImplementationPhase(phase: AutopilotChildPhase | null): boolean {\n  return phase === 'ultragoal' || phase === 'rework' || phase === 'team' || phase === 'ralph';\n}\n\nconst ALLOWED_ACTIVE_TRANSITIONS: Readonly<Record<AutopilotChildPhase, readonly AutopilotChildPhase[]>> = {\n  'deep-interview': ['deep-interview', 'ralplan'],\n  ralplan: ['ralplan', 'ultragoal'],","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/autopilot/completion-gate.ts#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"Flaky worker processes that crash and get respawned by automation, respawn hooks in tmux.conf, or persisted PIDs reused after session restart.","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"],"exampleFix":"# before\ntmux respawn-pane -k -t %4  # worker PID changes → later ops throw\n\n# after\n# relaunch through the library API so the pinned PID is refreshed","handlingStrategy":"fallback","validationCode":"const proof = readExactWorkerPaneLivenessProofSync(workerPaneId); if (proof.status === 'live' && proof.pid !== pinnedPid) planRelaunchOrRepin();","typeGuard":null,"tryCatchPattern":"catch (err) { if (/worker pane PID changed/.test(err.message)) { relaunchWorker(); } }","preventionTips":["Disable respawn-pane automation for workers","Fix worker crashes at the source","Re-pin PID only via a deliberate relaunch flow"],"tags":["tmux","pid-reuse","worker","respawn"],"backgroundTag":"process-pid-changed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}