{"record":{"id":"0be3df07ccb60ea9","repo":"paperclipai/paperclip","slug":"outcome-result-message-fallback-runner-prp-co","errorCode":null,"errorMessage":"${outcome.result.message} (fallback: runner_prp_command_${outcome.status}:${commandId})","messagePattern":"(.+?) \\(fallback: runner_prp_command_(.+?):(.+?)\\)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/runner-prp-coordinator.ts","lineNumber":409,"sourceCode":"                timer.unref();\n              }),\n            ]);\n          } finally {\n            if (timer) clearTimeout(timer);\n          }\n        },\n        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;","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/native-runtime/runner-prp-coordinator.ts#L391-L427","documentation":"When the awaited command reaches terminal status 'failed' or 'rejected', waitForCommand throws the outcome's own result.message when it is a string, otherwise a synthesized fallback 'runner_prp_command_failed:<id>' / 'runner_prp_command_rejected:<id>'. This error surfaces the runner's (or authority's) own failure reason for the command — the wait itself worked, the command did not succeed.","triggerScenarios":"The runner reported the command as failed (execution error) or rejected (policy/permission refusal, e.g. a blocked question or disallowed tool call); or the runner completed the command with an error result lacking a string 'message' field, triggering the fallback message.","commonSituations":"Runner-side command handlers throwing; governance rules rejecting a queued command type; deserialization producing a result without a message field so only the generic fallback appears; agent-side denial of a requested action.","solutions":["Read the error message — when it is not the fallback, it is the runner's own failure description; fix the root cause reported there (bad payload, missing permission, invalid command type).","If you only see the fallback 'runner_prp_command_failed:<id>', add runner-side logging or inspect run/heartbeat events to recover the underlying failure reason.","Ensure runner command handlers return results shaped as { message: string, ... } on failure so actionable messages propagate instead of the fallback.","Distinguish rejected (policy) from failed (execution): if rejected, remove or adjust the command type/payload against governance rules before re-issuing."],"exampleFix":"// before\nawait session.waitForCommand(commandId); // opaque fallback on failure\n\n// after\ntry {\n  await session.waitForCommand(commandId);\n} catch (e) {\n  if (/runner_prp_command_(failed|rejected):/.test(e.message)) {\n    logger.error({ commandId, reason: e.message }, 'command did not complete');\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const isCommandFailure = (e: unknown): e is Error =>\n  e instanceof Error && /^runner_prp_command_(failed|rejected):/.test(e.message);","tryCatchPattern":"try {\n  return await session.waitForCommand(commandId);\n} catch (e) {\n  if (isCommandFailure(e)) {\n    // message carries the runner's failure/rejection reason (or fallback)\n    log.warn({ commandId, reason: e.message }, 'command not completed');\n  }\n  throw e;\n}","preventionTips":["Make runner command handlers return { message: string } in every failure result so real causes surface instead of the fallback text.","Validate command payloads against the runner's accepted schema before queueing.","Check governance/policy rules before queueing command types that may be rejected.","Log full error messages centrally to distinguish failed (execution) from rejected (policy) outcomes."],"tags":["command-dispatch","native-runtime","upstream-failure"],"backgroundTag":"upstream-api-error","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}