{"record":{"id":"5d0a0fd0d10c2e28","repo":"paperclipai/paperclip","slug":"not-authorized","errorCode":"not_authorized","errorMessage":"Only the board or the assigned recovery owner can record watchdog decisions","messagePattern":"Only the board or the assigned recovery owner can record watchdog decisions","errorType":"error_code","errorClass":"WatchdogDecisionApplicationError","httpStatus":null,"severity":"error","filePath":"server/src/modules/active-run-watchdog/application/use-cases.ts","lineNumber":163,"sourceCode":"    if (input.actor.type === \"agent\" && !evaluationIssue) {\n      throw new WatchdogDecisionApplicationError(\n        \"evaluation_issue_required\",\n        \"Agent watchdog decisions require the target evaluation issue\",\n      );\n    }\n\n    const boardActor = input.actor.type === \"board\";\n    const assignedRecoveryOwner =\n      input.actor.type === \"agent\" &&\n      Boolean(input.actor.agentId) &&\n      evaluationIssue !== null &&\n      evaluationIssue.originKind === STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND &&\n      evaluationIssue.originId === run.id &&\n      evaluationIssue.hiddenAt === null &&\n      ![\"done\", \"cancelled\"].includes(evaluationIssue.status) &&\n      evaluationIssue.assigneeAgentId === input.actor.agentId;\n    if (!boardActor && !assignedRecoveryOwner) {\n      throw new WatchdogDecisionApplicationError(\n        \"not_authorized\",\n        \"Only the board or the assigned recovery owner can record watchdog decisions\",\n      );\n    }\n\n    if (evaluationIssue && (\n      evaluationIssue.originKind !== STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND ||\n      evaluationIssue.originId !== run.id\n    )) {\n      throw new WatchdogDecisionApplicationError(\n        \"evaluation_issue_mismatch\",\n        \"Watchdog decision evaluation issue is not bound to the target run\",\n      );\n    }\n\n    const createdByRunId = input.actor.type === \"agent\"\n      ? input.actor.runId ?? input.createdByRunId ?? null\n      : input.actor.type === \"board\"","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/modules/active-run-watchdog/application/use-cases.ts#L145-L181","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (agent not assigned)\nawait recordWatchdogDecision({ actor: { type: 'agent', agentId: helperAgentId }, ... });\n// after\nconst issue = await getEvaluationIssue(companyId, evaluationIssueId);\nconst actor = issue.assigneeAgentId === helperAgentId\n  ? { type: 'agent', agentId: helperAgentId }\n  : { type: 'board' };\nawait recordWatchdogDecision({ actor, ... });","handlingStrategy":"validation","validationCode":"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');","typeGuard":"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); }","tryCatchPattern":"try { await recordWatchdogDecision(input); } catch (e) { if (e.code === 'not_authorized') { /* fall back to board actor or reassign issue */ } else throw e; }","preventionTips":["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"],"tags":["authorization","permission","watchdog","agent"],"backgroundTag":"insufficient-permissions","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}