paperclipai/paperclip · error · WatchdogDecisionApplicationError
not_authorized
not_authorized
Error message
Only the board or the assigned recovery owner can record watchdog decisions
What it means
Watchdog decisions may only be recorded by the board or by the evaluation issue's assigned recovery owner (the agent whose assigneeAgentId matches the actor's agentId on a live stale-active-run evaluation issue bound to the run). When neither condition holds, the use case throws code not_authorized. This enforces the single-assignee recovery-owner model.
Source
Thrown at server/src/modules/active-run-watchdog/application/use-cases.ts:163
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) &&
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"View on GitHub (pinned to 01ad858492)
Solutions
- Verify the evaluation issue's assigneeAgentId equals the acting agent's ID before recording
- Use a board actor (board operator context) for decisions not tied to the assigned recovery owner
- If the assigned agent should change, reassign the evaluation issue first, then record the decision as the new assignee
- Confirm the issue is still live: hiddenAt null and status not done/cancelled
- Do not attempt to bypass with a different company or actor context — company scoping is enforced upstream
Example fix
// before (agent not assigned)
await recordWatchdogDecision({ actor: { type: 'agent', agentId: helperAgentId }, ... });
// after
const issue = await getEvaluationIssue(companyId, evaluationIssueId);
const actor = issue.assigneeAgentId === helperAgentId
? { type: 'agent', agentId: helperAgentId }
: { type: 'board' };
await recordWatchdogDecision({ actor, ... }); Defensive patterns
Strategy: validation
Validate before calling
const authorized = actor.type === 'board' || (issue && issue.assigneeAgentId === actor.agentId && issue.hiddenAt === null && !['done','cancelled'].includes(issue.status)); if (!authorized) throw new Error('only board or assigned recovery owner may record this decision'); Type guard
function isRecoveryOwner(issue: { assigneeAgentId?: string | null; hiddenAt: Date | null; status: string }, agentId: string) { return issue.assigneeAgentId === agentId && issue.hiddenAt === null && !['done','cancelled'].includes(issue.status); } Try / catch
try { await recordWatchdogDecision(input); } catch (e) { if (e.code === 'not_authorized') { /* fall back to board actor or reassign issue */ } else throw e; } Prevention
- Check assigneeAgentId against the acting agent before attempting to record
- Escalate to board context for non-assignee automation
- Reassign the evaluation issue rather than bypassing authorization
- Keep issue status fresh; closed issues revoke recovery-owner rights
When it happens
Trigger: An agent records a decision on a run whose stale-active-run evaluation issue is assigned to a different agent, or where the issue's originKind/originId don't match, the issue is hidden, done, or cancelled — making assignedRecoveryOwner false — while the actor is not a board actor.
Common situations: A backup agent answering recovery prompts intended for the primary assignee; agents acting after the evaluation issue was closed (done/cancelled) or hidden; automation using a service agent ID that is not the assignee; calling with board-only semantics using an agent key.
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- evaluation_issue_required
- creator_run_invalid
- agent_not_assigned
- agent_authorization_required
- Probe found no matching Anthropic Agent
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/5d0a0fd0d10c2e28.
Report an issue: GitHub.