paperclipai/paperclip · error · WatchdogDecisionApplicationError

run_not_found

run_not_found

Error message

Heartbeat run not found

What it means

createRecordWatchdogDecision looks up the heartbeat run with reader.findRunForCompany(companyId, runId); when no run matches — including runs that exist but belong to a different company — it throws WatchdogDecisionApplicationError with code run_not_found. This is a company-scoped not-found guard for recording watchdog decisions on active-run (heartbeat) runs.

Source

Thrown at server/src/modules/active-run-watchdog/application/use-cases.ts:137

export type RecordWatchdogDecisionUseCaseInput = {
  companyId: string;
  runId: string;
  actor: WatchdogDecisionActor;
  decision: "snooze" | "continue" | "dismissed_false_positive";
  evaluationIssueId?: string | null;
  reason?: string | null;
  snoozedUntil?: Date | null;
  createdByRunId?: string | null;
  now?: Date;
};

export function createRecordWatchdogDecision(deps: RecordWatchdogDecisionDeps) {
  return async function recordWatchdogDecision(
    input: RecordWatchdogDecisionUseCaseInput,
  ): Promise<WatchdogDecisionRecord> {
    const run = await deps.reader.findRunForCompany(input.companyId, input.runId);
    if (!run) throw new WatchdogDecisionApplicationError("run_not_found", "Heartbeat run not found");

    const evaluationIssue = input.evaluationIssueId
      ? await deps.reader.findEvaluationIssueById(input.companyId, input.evaluationIssueId)
      : null;
    if (input.evaluationIssueId && !evaluationIssue) {
      throw new WatchdogDecisionApplicationError("evaluation_issue_not_found", "Evaluation issue not found");
    }
    if (input.actor.type === "agent" && !evaluationIssue) {
      throw new WatchdogDecisionApplicationError(
        "evaluation_issue_required",
        "Agent watchdog decisions require the target evaluation issue",
      );
    }

    const boardActor = input.actor.type === "board";
    const assignedRecoveryOwner =
      input.actor.type === "agent" &&
      Boolean(input.actor.agentId) &&

View on GitHub (pinned to 01ad858492)

Solutions

  1. Verify the runId exists in the heartbeat runs table and belongs to the same companyId in the request context
  2. Re-fetch the current run list for the company to confirm the ID is still valid before recording a decision
  3. Check for cross-company context leakage: confirm the authenticated actor's companyId matches the run's company
  4. If the run was deleted legitimately, treat the decision as obsolete instead of retrying

Example fix

// before
await recordWatchdogDecision({ companyId: otherCompanyId, runId: cachedRunId, ... });
// after
const runs = await listHeartbeatRuns(companyId);
const run = runs.find(r => r.id === runId);
if (!run) throw new Error('run not found in company; refetch before recording decision');
await recordWatchdogDecision({ companyId, runId: run.id, ... });
Defensive patterns

Strategy: validation

Validate before calling

const run = await listHeartbeatRuns(companyId).then(rs => rs.find(r => r.id === runId)); if (!run) throw new Error(`run ${runId} not found for company ${companyId}`);

Type guard

null

Try / catch

try { await recordWatchdogDecision(input); } catch (e) { if (e.code === 'run_not_found') { /* refetch run list; abort decision as obsolete */ } else throw e; }

Prevention

When it happens

Trigger: Calling recordWatchdogDecision (via the active-run-watchdog API or createActiveRunWatchdog wiring) with a runId that does not exist, was deleted, is mistyped, or exists under a different companyId so the company-scoped query returns nothing.

Common situations: Clients caching run IDs from a different company context; decisions recorded after the heartbeat run was purged; UUID truncated/transformed by a proxy; passing a heartbeat ID vs run ID confusion between tables.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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