paperclipai/paperclip · error

runnerReconnectGraceMs must be a positive safe integer

Error message

runnerReconnectGraceMs must be a positive safe integer

What it means

awaitAdoptedRunnerAuthentication waits for an adopted (already-running) native runner to authenticate over PRP within a grace period. Before starting the wait it validates timeoutMs (runnerReconnectGraceMs) and requires a positive safe integer; zero, negative, non-integer, NaN, or out-of-safe-range values throw this error immediately rather than producing a broken deadline.

Source

Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:809

  // immediately before force-killing the runner. The suspension proof itself
  // must retain a finite opportunity to cross the durable command boundary.
  const suspensionReserveMs = Math.min(2_500, Math.ceil(graceMs / 2));
  return {
    preparationDeadline: startedAtMs + graceMs - suspensionReserveMs,
    closeDeadline: startedAtMs + graceMs,
  };
}

async function awaitAdoptedRunnerAuthentication(input: {
  activeConnectionCount: () => number;
  isAlive: () => Promise<boolean> | boolean;
  throwIfFailed: () => void;
  failure: Promise<never>;
  ready?: () => Promise<void>;
  timeoutMs: number;
}): Promise<void> {
  if (!Number.isSafeInteger(input.timeoutMs) || input.timeoutMs <= 0) {
    throw new Error("runnerReconnectGraceMs must be a positive safe integer");
  }
  const deadline = Date.now() + input.timeoutMs;
  const timeoutError = () =>
    new Error(
      "native_adopted_runner_authentication_timeout: the existing runner did not authenticate within " +
        `${input.timeoutMs}ms; preserve its process and durable session for operator recovery`,
    );
  let cancelled = false;
  let pollTimer: NodeJS.Timeout | undefined;
  let deadlineTimer: NodeJS.Timeout | undefined;
  const checkDeadline = () => {
    if (Date.now() >= deadline) throw timeoutError();
  };
  const observe = async () => {
    await input.ready?.();
    while (!cancelled) {
      input.throwIfFailed();
      checkDeadline();

View on GitHub (pinned to 01ad858492)

Solutions

  1. Set runnerReconnectGraceMs to a positive integer number of milliseconds (e.g. 30000)
  2. Validate/parse the config value with Number.isSafeInteger before passing it in
  3. Fix the config source so the value is parsed as a number, not a string or undefined
  4. Choose a grace period large enough for the runner to authenticate (not 0 or negative)

Example fix

// before
awaitAdoptedRunnerAuthentication({ timeoutMs: Number(process.env.RUNNER_RECONNECT_GRACE_MS) }); // NaN/0 possible
// after
const graceMs = Number.parseInt(process.env.RUNNER_RECONNECT_GRACE_MS ?? "30000", 10);
if (!Number.isSafeInteger(graceMs) || graceMs <= 0) throw new Error("invalid runnerReconnectGraceMs");
awaitAdoptedRunnerAuthentication({ timeoutMs: graceMs });
Defensive patterns

Strategy: validation

Validate before calling

const graceMs = config.runnerReconnectGraceMs;
if (!Number.isSafeInteger(graceMs) || graceMs <= 0) {
  throw new Error(`runnerReconnectGraceMs must be a positive safe integer, got ${graceMs}`);
}

Type guard

const isValidGraceMs = (v: unknown): v is number =>
  typeof v === "number" && Number.isSafeInteger(v) && v > 0;

Prevention

When it happens

Trigger: Calling awaitAdoptedRunnerAuthentication (via #awaitAdoptedRunnerConnection) with timeoutMs equal to 0, negative, non-integer (e.g. 1500.5), NaN, Infinity, or a value beyond Number.MAX_SAFE_INTEGER.

Common situations: Config value runnerReconnectGraceMs parsed from environment/config as a string or float instead of an integer; unset config defaulting to NaN after Number(undefined); a user setting 0 intending 'no wait'; unit conversions producing fractional milliseconds.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/472cedf81c946508. Report an issue: GitHub.