paperclipai/paperclip · error
runner_prp_session_released
runner_prp_session_released
Error message
runner_prp_session_released
What it means
The PRP coordinator session object returned by runnerPrpCoordinator carries a 'released' flag that is set when the session's registration is released (runner disconnected, run finished, or teardown). Every public method — queueCommand, completeRun, waitForCommand, waitForGoalEvent — throws 'runner_prp_session_released' when invoked after release. It means the caller is using a stale session handle after its lifetime ended.
Source
Thrown at server/src/services/native-runtime/runner-prp-coordinator.ts:399
}
let timer: NodeJS.Timeout | null = null;
try {
return await Promise.race([
terminalEvent,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(
() => reject(new Error("runner_prp_terminal_timeout")),
timeoutMs,
);
timer.unref();
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
},
waitForCommand: async (commandId, timeoutMs = 30_000) => {
if (released) throw new Error("runner_prp_session_released");
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const outcome = authority.commandOutcome(commandId);
if (!outcome) throw new Error(`runner_prp_command_missing:${commandId}`);
if (outcome.status === "completed") return outcome.result;
if (outcome.status === "failed" || outcome.status === "rejected") {
const message = outcome.result && typeof outcome.result.message === "string"
? outcome.result.message
: `runner_prp_command_${outcome.status}:${commandId}`;
throw new Error(message);
}
await new Promise<void>((resolve) => {
const timer = setTimeout(resolve, 10);
timer.unref();
});
}
throw new Error(`runner_prp_command_timeout:${commandId}`);
},View on GitHub (pinned to 01ad858492)
Solutions
- Check session liveness before each use, or wrap calls in try/catch for /runner_prp_session_released/ and abort the loop instead of retrying.
- Keep a single owner of the session lifetime; cancel dependent waiters when release happens rather than sharing the handle across tasks.
- Re-acquire a fresh coordinator session (re-run runnerPrpCoordinator / re-register the authority) if the run is still active and commands must be sent.
- Verify the runner is still connected before issuing commands; a released session usually means the runner already disconnected.
Example fix
// before
const result = await session.waitForCommand(commandId, 30_000);
// after
let result;
try {
result = await session.waitForCommand(commandId, 30_000);
} catch (e) {
if (e.message === 'runner_prp_session_released') return; // session gone; stop
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (session.isReleased?.()) return; // track release state if exposed const stillActive = !runFinished && runnerConnected; // guard with your own lifecycle flags
Type guard
const isSessionReleased = (e: unknown): e is Error => e instanceof Error && e.message === 'runner_prp_session_released';
Try / catch
try {
await session.waitForCommand(commandId);
} catch (e) {
if (isSessionReleased(e)) {
// session torn down: stop work, do NOT retry
return null;
}
throw e;
} Prevention
- Scope session usage to the run's lifetime; cancel dependent tasks on release.
- Avoid storing session handles in long-lived registries or globals.
- Subscribe to the coordinator's terminal/disconnect events to stop issuing commands proactively.
- Never reuse a session object after completeRun resolves.
When it happens
Trigger: Calling session.waitForCommand(commandId) (or queueCommand/completeRun/waitForTerminal/waitForGoalEvent) after the coordinator's release path ran — e.g. the runner websocket disconnected and registration.release() was invoked, or the run already completed and the coordinator tore the session down — while an in-flight async continuation still holds the old session object.
Common situations: Background polling loops that outlive the run; awaiting waitForCommand with a long timeout while a concurrent disconnect triggers release; retry logic reusing a captured session after a failed await; shutdown handlers releasing sessions while workers still reference them.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ${name} must be a JSON object
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
- --file is required
- Request failed with status ${response.status}
- Failed to serve file
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/5125828ffbe990a7.
Report an issue: GitHub.