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
- Re-read the evaluation issue and confirm originId matches the runId you are recording against
- Refetch the correct stale-active-run evaluation issue for the current run (it is recreated per run) instead of caching across runs
- If the run was replaced, locate the new run's evaluation issue before recording
- 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
- Look up the evaluation issue by (companyId, runId) origin binding rather than caching IDs across runs
- Invalidate cached issue IDs whenever a run is retried or recreated
- Assert originKind/originId in client code before submitting the decision
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
- ACPX provider dependency ancestry is invalid
- ACPX provider runtime executable count is invalid
- evaluation_issue_not_found
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- "tool" is required and must be a string
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/b594db491d853210.
Report an issue: GitHub.