{"record":{"id":"069124191efde07b","repo":"paperclipai/paperclip","slug":"options-errorlabel-decision-predicate-exited-0-069124","errorCode":null,"errorMessage":"${options.errorLabel} decision predicate exited 0 (expected 10, 20, 21, or 22)","messagePattern":"(.+?) decision predicate exited 0 \\(expected 10, 20, 21, or 22\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapters/grok-local/src/server/grok-auth-merge-decision.ts","lineNumber":74,"sourceCode":"    await execFile(\"node\", [DECISION_SCRIPT_PATH, sourcePath, destinationPath]);\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\n  // exits 10, 20, 21, 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, 21, or 22)`,\n  );\n}\n","sourceCodeStart":56,"sourceCodeEnd":78,"githubUrl":"https://github.com/paperclipai/paperclip/blob/5716fe907e596ce73501408fc6efdb19fb61edf2/packages/adapters/grok-local/src/server/grok-auth-merge-decision.ts#L56-L78","documentation":"If execFile resolves, the predicate exited 0 — but the contract requires it to always exit 10, 20, 21, or 22. This throw fails loud on a contract violation: an exit-0 predicate cannot be mapped to any merge decision, so treating it as \"keep destination\" would silently corrupt credential freshness logic.","triggerScenarios":"The predicate script was replaced/patched to `process.exit(0)` or to fall off the end of main without exiting a decision code; a wrapper (shell alias, supervisor) swallows the real exit code and returns 0; an edited .cjs hits a code path that returns normally.","commonSituations":"Someone hand-edited the decision script for testing and left exit 0 in place; a monorepo build overwrote the .cjs with a stub; running under a tool that rewrites exit codes (some shims/wrappers normalize to 0).","solutions":["Inspect grok-auth-merge-decision.cjs and restore exits 10/20/21/22 on every code path.","Verify nothing wraps the script (shell wrappers, supervisors) that could normalize the exit code to 0.","Reinstall/restore the original file from the package or repository.","Add a local test invoking the predicate on fixture files and asserting the exit code is one of 10/20/21/22."],"exampleFix":"// before (broken predicate)\nif (fresh) process.exit(10);\n// falls through and exits 0\n// after\nclassify();\nprocess.exit(USE_SOURCE_EXIT); // every path exits 10, 20, 21, or 22","handlingStrategy":"validation","validationCode":"// verify the predicate honors its contract on fixtures before relying on it\nconst { exitCode } = await execFile(\"node\", [DECISION_SCRIPT_PATH, fixtureSrc, fixtureDst]).catch(e => e);\nif (![10, 20, 21, 22].includes(Number(exitCode))) {\n  throw new Error(`predicate contract broken: exited ${exitCode}`);\n}","typeGuard":"function isKnownDecisionCode(code: unknown): code is 10 | 20 | 21 | 22 {\n  return code === 10 || code === 20 || code === 21 || code === 22;\n}","tryCatchPattern":"try {\n  const decision = await decideGrokAuthMerge(src, dst, { errorLabel: \"grok auth copy-out\" });\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"exited 0\")) {\n    failClosed(); // refuse the merge — do not guess a decision\n  }\n  throw e;\n}","preventionTips":["Restore the original .cjs if it was patched or stubbed by a build step.","Add a unit test asserting every code path exits 10/20/21/22.","Avoid wrappers/supervisors that can rewrite child exit codes to 0.","Fail closed on any non-contract exit; never default to a merge decision."],"tags":["child-process","exit-code","contract-violation","auth","predicate"],"backgroundTag":"unexpected-exit-code","analyzedSha":"5716fe907e596ce73501408fc6efdb19fb61edf2","analyzedAt":"2026-09-02T18:44:00.616Z","contentChangedAt":"2026-09-02T18:44:00.616Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}