{"record":{"id":"946329e1e95b89af","repo":"paperclipai/paperclip","slug":"runner-prp-command-timeout","errorCode":"runner_prp_command_timeout","errorMessage":"runner_prp_command_timeout:${commandId}","messagePattern":"runner_prp_command_timeout:(.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/services/native-runtime/runner-prp-coordinator.ts","lineNumber":416,"sourceCode":"        waitForCommand: async (commandId, timeoutMs = 30_000) => {\n          if (released) throw new Error(\"runner_prp_session_released\");\n          const deadline = Date.now() + timeoutMs;\n          while (Date.now() < deadline) {\n            const outcome = authority.commandOutcome(commandId);\n            if (!outcome) throw new Error(`runner_prp_command_missing:${commandId}`);\n            if (outcome.status === \"completed\") return outcome.result;\n            if (outcome.status === \"failed\" || outcome.status === \"rejected\") {\n              const message = outcome.result && typeof outcome.result.message === \"string\"\n                ? outcome.result.message\n                : `runner_prp_command_${outcome.status}:${commandId}`;\n              throw new Error(message);\n            }\n            await new Promise<void>((resolve) => {\n              const timer = setTimeout(resolve, 10);\n              timer.unref();\n            });\n          }\n          throw new Error(`runner_prp_command_timeout:${commandId}`);\n        },\n        waitForGoalEvent: async (requestId, timeoutMs = 30_000) => {\n          if (released) throw new Error(\"runner_prp_session_released\");\n          if (observedGoalRequests.has(requestId)) {\n            const error = observedGoalRequests.get(requestId);\n            if (error) throw new Error(error);\n            return;\n          }\n          let timer: NodeJS.Timeout | null = null;\n          let resolveGoalEvent!: () => void;\n          let rejectGoalEvent!: (error: Error) => void;\n          const goalEvent = new Promise<void>((resolve, reject) => {\n            resolveGoalEvent = resolve;\n            rejectGoalEvent = reject;\n          });\n          const waiter = { resolve: resolveGoalEvent, reject: rejectGoalEvent };\n          const waiters = goalEventWaiters.get(requestId) ?? new Set<typeof waiter>();\n          waiters.add(waiter);","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/native-runtime/runner-prp-coordinator.ts#L398-L434","documentation":"waitForCommand polls the authority for the command outcome every 10ms until deadline (default 30s). If the command still has no terminal outcome when the deadline passes, it throws 'runner_prp_command_timeout:<id>'. This means the command exists but the runner never completed, failed, or rejected it within the allotted window.","triggerScenarios":"Runner is connected but slow or wedged (long-running command exceeding the 30s default); the runner disconnected mid-command so the outcome never arrives; the caller passes an explicit timeoutMs that is too short; command queue backlog delays processing past the deadline.","commonSituations":"Waiting on an interactive user-question command with the default 30s while the human takes longer; network stall between controller and runner; an overloaded runner worker not draining the command queue; passing timeoutMs=30000 explicitly for operations known to take minutes.","solutions":["Increase the timeoutMs argument to match the expected command duration (e.g. 300_000 for human-in-the-loop questions).","Catch /runner_prp_command_timeout:(.+)/ and re-poll via a fresh waitForCommand or check the outcome out-of-band instead of failing the run immediately.","Verify the runner connection is healthy; after a disconnect the outcome will never arrive, so re-queue the command on a new session rather than waiting.","Check runner-side processing logs for the commandId to find why it never reached a terminal state; fix the wedged handler."],"exampleFix":"// before\nconst result = await session.waitForCommand(commandId); // default 30s\n\n// after\nconst result = await session.waitForCommand(commandId, 5 * 60_000); // allow 5 min","handlingStrategy":"retry","validationCode":"// size the timeout to the command type before waiting:\nconst TIMEOUT_BY_TYPE = { question: 5 * 60_000, tool_call: 30_000 };\nawait session.waitForCommand(commandId, TIMEOUT_BY_TYPE[type] ?? 30_000);","typeGuard":"const isCommandTimeout = (e: unknown): e is Error =>\n  e instanceof Error && /^runner_prp_command_timeout:/.test(e.message);","tryCatchPattern":"async function waitWithRetry(session, commandId, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await session.waitForCommand(commandId, 30_000);\n    } catch (e) {\n      if (isCommandTimeout(e) && i < attempts - 1) continue;\n      throw e;\n    }\n  }\n}","preventionTips":["Choose timeoutMs per command type; the 30s default is too short for human-in-the-loop commands.","Monitor runner connectivity so you can distinguish slow commands from dead connections.","Add progress events/checkpoints on the runner for long operations so outcomes arrive before the deadline.","Alert on repeated command timeouts — they usually indicate a wedged runner worker, not transient latency."],"tags":["timeout","command-dispatch","native-runtime"],"backgroundTag":"request-timeout","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}