paperclipai/paperclip · error

${outcome.result.message} (fallback: runner_prp_command_${ou

Error message

${outcome.result.message} (fallback: runner_prp_command_${outcome.status}:${commandId})

What it means

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.

Source

Thrown at server/src/services/native-runtime/runner-prp-coordinator.ts:409

                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}`);
        },
        waitForGoalEvent: async (requestId, timeoutMs = 30_000) => {
          if (released) throw new Error("runner_prp_session_released");
          if (observedGoalRequests.has(requestId)) {
            const error = observedGoalRequests.get(requestId);
            if (error) throw new Error(error);
            return;
          }
          let timer: NodeJS.Timeout | null = null;
          let resolveGoalEvent!: () => void;
          let rejectGoalEvent!: (error: Error) => void;

View on GitHub (pinned to 01ad858492)

Solutions

  1. 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).
  2. 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.
  3. Ensure runner command handlers return results shaped as { message: string, ... } on failure so actionable messages propagate instead of the fallback.
  4. Distinguish rejected (policy) from failed (execution): if rejected, remove or adjust the command type/payload against governance rules before re-issuing.

Example fix

// before
await session.waitForCommand(commandId); // opaque fallback on failure

// after
try {
  await session.waitForCommand(commandId);
} catch (e) {
  if (/runner_prp_command_(failed|rejected):/.test(e.message)) {
    logger.error({ commandId, reason: e.message }, 'command did not complete');
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

const isCommandFailure = (e: unknown): e is Error =>
  e instanceof Error && /^runner_prp_command_(failed|rejected):/.test(e.message);

Try / catch

try {
  return await session.waitForCommand(commandId);
} catch (e) {
  if (isCommandFailure(e)) {
    // message carries the runner's failure/rejection reason (or fallback)
    log.warn({ commandId, reason: e.message }, 'command not completed');
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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