paperclipai/paperclip · error · WatchdogDecisionApplicationError

evaluation_issue_mismatch

evaluation_issue_mismatch

Error message

Watchdog decision evaluation issue is not bound to the target run

What it means

When a decision references an evaluation issue, that issue must be a stale-active-run evaluation bound to the exact heartbeat run targeted by the decision (originKind === STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND and originId === run.id). Otherwise the use case throws code evaluation_issue_mismatch, preventing decisions on one run from being laundered through another run's evaluation issue.

Source

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

      Boolean(input.actor.agentId) &&
      evaluationIssue !== null &&
      evaluationIssue.originKind === STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND &&
      evaluationIssue.originId === run.id &&
      evaluationIssue.hiddenAt === null &&
      !["done", "cancelled"].includes(evaluationIssue.status) &&
      evaluationIssue.assigneeAgentId === input.actor.agentId;
    if (!boardActor && !assignedRecoveryOwner) {
      throw new WatchdogDecisionApplicationError(
        "not_authorized",
        "Only the board or the assigned recovery owner can record watchdog decisions",
      );
    }

    if (evaluationIssue && (
      evaluationIssue.originKind !== STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND ||
      evaluationIssue.originId !== run.id
    )) {
      throw new WatchdogDecisionApplicationError(
        "evaluation_issue_mismatch",
        "Watchdog decision evaluation issue is not bound to the target run",
      );
    }

    const createdByRunId = input.actor.type === "agent"
      ? input.actor.runId ?? input.createdByRunId ?? null
      : input.actor.type === "board"
        ? input.actor.runId ?? input.createdByRunId ?? null
        : null;
    if (createdByRunId) {
      const creatorRun = await deps.reader.findRunForCompany(input.companyId, createdByRunId);
      const sameAgent = input.actor.type !== "agent" || creatorRun?.agentId === input.actor.agentId;
      if (!creatorRun || !sameAgent) {
        throw new WatchdogDecisionApplicationError(
          "creator_run_invalid",
          "createdByRunId is not valid for this watchdog decision actor",
        );

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-read the evaluation issue and confirm originId matches the runId you are recording against
  2. Refetch the correct stale-active-run evaluation issue for the current run (it is recreated per run) instead of caching across runs
  3. If the run was replaced, locate the new run's evaluation issue before recording
  4. Verify originKind is STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND, not another issue origin

Example fix

// before
await recordWatchdogDecision({ runId: newRunId, evaluationIssueId: oldRunIssueId, ... });
// after
const issue = await findStaleActiveRunEvaluation(companyId, newRunId); // originId === newRunId
await recordWatchdogDecision({ runId: newRunId, evaluationIssueId: issue.id, ... });
Defensive patterns

Strategy: validation

Validate before calling

if (issue && (issue.originKind !== 'stale_active_run' || issue.originId !== runId)) throw new Error('evaluation issue is not bound to target run');

Type guard

function isBoundToRun(issue: { originKind: string; originId: string }, runId: string, originKind: string) { return issue.originKind === originKind && issue.originId === runId; }

Try / catch

try { await recordWatchdogDecision(input); } catch (e) { if (e.code === 'evaluation_issue_mismatch') { /* refetch the evaluation issue for the current runId */ } else throw e; }

Prevention

When it happens

Trigger: recordWatchdogDecision called with an evaluationIssueId whose originId points to a different heartbeat run than input.runId, or whose originKind is not the stale-active-run evaluation kind (e.g. a generic issue or a different origin type).

Common situations: UI or scripts reusing an old evaluation issue ID with a new run ID after a run was retried/recreated; mixing up run IDs across companies or heartbeat cycles; passing a non-watchdog issue ID that happens to exist in the company.

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


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