{"record":{"id":"78d8b12050be3ed8","repo":"paperclipai/paperclip","slug":"options-errorlabel-decision-predicate-exited-0-expected-10","errorCode":null,"errorMessage":"${options.errorLabel} decision predicate exited 0 (expected 10, 20, or 22)","messagePattern":"(.+?) decision predicate exited 0 \\(expected 10, 20, or 22\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapters/codex-local/src/server/codex-auth-merge-decision.ts","lineNumber":77,"sourceCode":"    await execFile(\"node\", args);\n  } catch (error) {\n    const code = (error as { code?: unknown }).code;\n    if (typeof code === \"number\" && KNOWN_EXIT_CODES.has(code)) {\n      return code;\n    }\n    const detail =\n      typeof code === \"string\"\n        ? `node could not be executed (${code})`\n        : typeof code === \"number\"\n          ? `unexpected predicate exit code ${code}`\n          : error instanceof Error\n            ? error.message\n            : String(error);\n    throw new Error(`${options.errorLabel} decision predicate failed: ${detail}`);\n  }\n  // `execFile` resolved, so the predicate exited 0. The predicate always exits\n  // 10, 20, or 22, so a clean exit 0 is unexpected; fail loud.\n  throw new Error(\n    `${options.errorLabel} decision predicate exited 0 (expected 10, 20, or 22)`,\n  );\n}\n","sourceCodeStart":59,"sourceCodeEnd":81,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/adapters/codex-local/src/server/codex-auth-merge-decision.ts#L59-L81","documentation":"The codex auth merge decision delegates to an external predicate script that must exit 10, 20, or 22 to signal its decision. If execFile resolves with exit code 0, the predicate violated its contract, so the wrapper fails loudly rather than guessing a merge decision. This is an internal invariant check on the predicate protocol.","triggerScenarios":"decideCodexAuthMerge runs the decision predicate and it exits 0 — e.g. the script was replaced by a stub, a bug added an early successful return, or the wrong binary is on PATH.","commonSituations":"A local patch or wrapper script around the predicate swallows the exit code; predicate updated/renamed so a different binary (e.g. a shell no-op) is found first; refactor removed the explicit exit(10/20/22) call paths.","solutions":["Inspect/repair the predicate script so every path exits 10, 20, or 22.","Check PATH/shims for a shadowing binary and run the intended predicate directly.","Run the predicate manually with the same args to see why it exits 0.","Pin/restore the expected predicate version shipped with the adapter."],"exampleFix":"// before (predicate script)\nmain();\n// after\nmain().then(code => process.exit(code)).catch(err => { console.error(err); process.exit(1); });","handlingStrategy":"try-catch","validationCode":"const r = cp.spawnSync(predicatePath, args);\nif (r.status === 0) throw new Error(\"predicate exited 0; expected 10/20/22\");","typeGuard":null,"tryCatchPattern":"try { decision = await decideCodexAuthMerge(opts); } catch (e) {\n  if (e.message.includes(\"exited 0\")) { console.error(\"predicate contract violated — check PATH and script version\"); }\n  throw e;\n}","preventionTips":["Ensure every predicate code path calls process.exit(10|20|22)","Verify PATH/shims don't shadow the predicate binary","Manually run the predicate with representative args after edits","Pin the predicate script version in CI"],"tags":["codex","predicate","exit-code","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}