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
- Set runnerReconnectGraceMs to a positive integer number of milliseconds (e.g. 30000)
- Validate/parse the config value with Number.isSafeInteger before passing it in
- Fix the config source so the value is parsed as a number, not a string or undefined
- 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
- Parse grace-period config with Number.parseInt and validate before use
- Default to a sane positive value when config is absent
- Reject 0/negative/float values at config-load time, not at call time
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
- ${prefix}: "timeoutPolicy" must be one of ${ADAPTER_LOGIN_TI
- ${label} is not a regular file at ${canonical}.
- Claude Managed evals require exact model claude-sonnet-5
- AWS AgentCore evals require exact model global.anthropic.cla
- request.session.provider must match request.provider
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/472cedf81c946508.
Report an issue: GitHub.