{"record":{"id":"413fa07ac865ca2b","repo":"paperclipai/paperclip","slug":"paperclip-runner-tool-binding-not-authorized","errorCode":"paperclip_runner_tool_binding_not_authorized","errorMessage":"paperclip_runner_tool_binding_not_authorized","messagePattern":"paperclip_runner_tool_binding_not_authorized","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/chat-attachment-reuse.ts","lineNumber":1054,"sourceCode":"          eq(heartbeatRuns.id, input.binding.runId),\n          eq(heartbeatRuns.companyId, input.binding.companyId),\n          eq(heartbeatRuns.agentId, input.binding.agentId),\n          eq(heartbeatRuns.nativeIssueId, input.binding.issueId),\n          eq(heartbeatRuns.runtimeMode, \"native\"),\n          eq(heartbeatRuns.status, \"running\"),\n          eq(issues.assigneeAgentId, input.binding.agentId),\n          eq(issues.executionRunId, input.binding.runId),\n        ),\n      )\n      .for(\"update\")\n      .limit(1);\n    if (\n      !current ||\n      [\"paused\", \"terminated\", \"pending_approval\", \"error\"].includes(\n        current.actorStatus,\n      )\n    ) {\n      throw new Error(\"paperclip_runner_tool_binding_not_authorized\");\n    }\n    const conversation = await authorizeChatConversationForBoundRun(\n      tx as unknown as Db,\n      input.binding,\n      current.run.contextSnapshot,\n    );\n    const sourceFilter = input.sourceCommentId ?? null;\n    const cursor = decodeListCursor(\n      input.cursor,\n      conversation.conversationId,\n      sourceFilter,\n    );\n    const rawCandidates = await tx.execute(sql<{\n      attachment_id: string;\n      source_comment_id: string;\n      created_at: Date | string;\n    }>`\n      with candidate_pairs as (","sourceCodeStart":1036,"sourceCodeEnd":1072,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/chat-attachment-reuse.ts#L1036-L1072","documentation":"Thrown by listAuthorizedChatAttachments in chat-attachment-reuse.ts:1054 when the run/actor binding check fails. The service looks up the heartbeat run FOR UPDATE joined to the issue and agent, requiring the run to belong to the binding's company/agent/issue, be in native runtime mode, status 'running', the issue's executionRunId to match, and the agent to be the issue assignee. If no such row is found, or the agent's status is paused, terminated, pending_approval, or error, the tool binding is considered no longer authorized and listing chat attachments is refused.","triggerScenarios":"The runner tool 'execute' path calls listAuthorizedChatAttachments with a ChatReuseBinding when: the run has finished or been superseded (heartbeat_runs.status != 'running' or issues.executionRunId no longer equals binding.runId); the agent actor is paused/terminated/pending_approval/error; the binding references a run from another company/agent/issue; or runtimeMode is not 'native'.","commonSituations":"A runner process continues calling tools after the operator paused the agent or the issue hit a budget/approval pause (pending_approval); the run completed between the wake and the tool call; an old runner binary replays a stale binding id; a company-scoping mistake sends a binding from another issue.","solutions":["Check the agent/actor status: if paused, terminated, pending_approval, or error, resolve that condition first (approve the pending action, resume the agent, clear the budget auto-pause) — the tool call cannot succeed while the actor is in those states.","Verify the run is still the issue's active execution run (heartbeat_runs.status='running' and issues.execution_run_id = binding.runId); if the run finished, re-checkout/re-execute the issue to get a fresh binding instead of reusing the stale one.","Confirm the ChatReuseBinding fields (companyId, agentId, runId, issueId) all come from the current run's context and are not stale or cross-company; regenerate the binding from the current run.","If this appears right after a pause/resume, retry after the actor status returns to an active state rather than looping the tool call."],"exampleFix":"// before: calling the tool after the run was paused\nconst page = await listAuthorizedChatAttachments({ db, binding: staleBinding, limit: 20 });\n// -> paperclip_runner_tool_binding_not_authorized (actorStatus: 'paused')\n\n// after: re-bind from a live execution run\nconst freshBinding = await getRunChatBinding({ runId: currentExecutionRunId }); // valid only while running & actor active\nconst page = await listAuthorizedChatAttachments({ db, binding: freshBinding, limit: 20 });","handlingStrategy":"try-catch","validationCode":"// before listing, confirm the run/actor are still live\nconst run = await getHeartbeatRun(binding.runId);\nconst issue = await getIssue(binding.issueId);\nconst agent = await getAgent(binding.agentId);\nconst ok = run.status === \"running\" && run.runtimeMode === \"native\"\n  && issue.executionRunId === binding.runId && issue.assigneeAgentId === binding.agentId\n  && ![\"paused\", \"terminated\", \"pending_approval\", \"error\"].includes(agent.status);\nif (!ok) throw new Error(\"skip: tool binding no longer authorized\");","typeGuard":"function isBindingNotAuthorized(err: unknown): err is Error {\n  return err instanceof Error && err.message === \"paperclip_runner_tool_binding_not_authorized\";\n}","tryCatchPattern":"try {\n  return await listAuthorizedChatAttachments({ db, binding, limit });\n} catch (err) {\n  if (err instanceof Error && err.message === \"paperclip_runner_tool_binding_not_authorized\") {\n    // stop tool activity for this run; do not retry until actor resumes or a new run binds the issue\n    return { status: \"binding-revoked\" as const };\n  }\n  throw err;\n}","preventionTips":["Check agent actor status (paused/terminated/pending_approval/error) before every tool call in the runner loop.","Regenerate the ChatReuseBinding from the current execution run instead of persisting bindings across runs.","Halt runner tool activity when heartbeat run status leaves 'running' or issues.execution_run_id changes.","Handle budget auto-pause and approval gates as terminal for in-flight tool calls; resume via a fresh run."],"tags":["authorization","binding","run-lifecycle","agent-status","native-runtime"],"backgroundTag":"insufficient-permissions","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}