paperclipai/paperclip · error

${options.errorLabel} decision predicate failed: ${detail}

Error message

${options.errorLabel} decision predicate failed: ${detail}

What it means

decideGrokAuthMerge runs a child `node` predicate (grok-auth-merge-decision.cjs) that must exit 10, 20, 21, or 22. If the process fails to spawn, crashes with another exit code, or errors, the wrapper throws this labeled error so a broken predicate is never silently treated as a merge decision. The message embeds the underlying detail (spawn failure, unexpected exit code, or error message).

Source

Thrown at packages/adapters/grok-local/src/server/grok-auth-merge-decision.ts:70

  destinationPath: string,
  options: DecideGrokAuthMergeOptions,
): Promise<number> {
  try {
    await execFile("node", [DECISION_SCRIPT_PATH, sourcePath, destinationPath]);
  } catch (error) {
    const code = (error as { code?: unknown }).code;
    if (typeof code === "number" && KNOWN_EXIT_CODES.has(code)) {
      return code;
    }
    const detail =
      typeof code === "string"
        ? `node could not be executed (${code})`
        : typeof code === "number"
          ? `unexpected predicate exit code ${code}`
          : error instanceof Error
            ? error.message
            : String(error);
    throw new Error(`${options.errorLabel} decision predicate failed: ${detail}`);
  }
  // `execFile` resolved, so the predicate exited 0. The predicate always
  // exits 10, 20, 21, or 22, so a clean exit 0 is unexpected; fail loud.
  throw new Error(
    `${options.errorLabel} decision predicate exited 0 (expected 10, 20, 21, or 22)`,
  );
}

View on GitHub (pinned to 5716fe907e)

Solutions

  1. Confirm `node --version` works in the same environment the server runs in; install node or fix PATH.
  2. Verify grok-auth-merge-decision.cjs exists next to the compiled module (check your build/bundling step).
  3. Run the predicate manually: `node grok-auth-merge-decision.cjs <src> <dst>; echo $?` to see the real crash.
  4. Fix the source/destination paths passed in if the predicate is crashing on file access.

Example fix

// before: spawn fails because node is missing in the packaged app
await execFile("node", [script, src, dst]);
// after: resolve an explicit runtime and verify availability first
const nodeBin = process.env.NODE_BIN ?? "node";
await execFile(nodeBin, [script, src, dst]); // and ship the .cjs with the bundle
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: node exists and the predicate script is present
import { accessSync, constants } from "node:fs";
try { accessSync(DECISION_SCRIPT_PATH, constants.X_OK); } catch { throw new Error("decision predicate missing: " + DECISION_SCRIPT_PATH); }
await execFile("node", ["--version"]); // throws ENOENT if node is unavailable

Type guard

function isSpawnError(e: unknown): e is NodeJS.ErrnoException {
  return e instanceof Error && typeof (e as NodeJS.ErrnoException).code === "string";
}

Try / catch

try {
  const code = await decideGrokAuthMerge(src, dst, { errorLabel: "grok auth copy-out" });
} catch (e) {
  if (e instanceof Error && e.message.includes("decision predicate failed")) {
    // never assume "keep destination" — a broken predicate is a hard failure
    log.error(e.message); // includes node spawn error or unexpected exit code
  }
  throw e;
}

Prevention

When it happens

Trigger: `node` is not on PATH or not executable (code is a string like ENOENT/EACCES); the predicate script crashed with an exit code outside {10,20,21,22} (e.g. uncaught exception → exit 1); the .cjs file is missing from the build output.

Common situations: Deploying/packaging without copying grok-auth-merge-decision.cjs; running in an environment without node in PATH (bundled/electron runtime); corrupted source/destination paths causing the predicate to throw; syntax error in a locally patched predicate.

Related errors


AI-assisted analysis of paperclipai/paperclip@5716fe907e (2026-09-02). Data as JSON: /api/errors/a4f0ef8ae51ad686. Report an issue: GitHub.