paperclipai/paperclip · error
paperclip_runner_tool_binding_not_authorized
paperclip_runner_tool_binding_not_authorized
Error message
paperclip_runner_tool_binding_not_authorized
What it means
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.
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.
Example fix
// before: calling the tool after the run was paused
const page = await listAuthorizedChatAttachments({ db, binding: staleBinding, limit: 20 });
// -> paperclip_runner_tool_binding_not_authorized (actorStatus: 'paused')
// after: re-bind from a live execution run
const freshBinding = await getRunChatBinding({ runId: currentExecutionRunId }); // valid only while running & actor active
const page = await listAuthorizedChatAttachments({ db, binding: freshBinding, limit: 20 }); Defensive patterns
Strategy: try-catch
Validate before calling
// before listing, confirm the run/actor are still live
const run = await getHeartbeatRun(binding.runId);
const issue = await getIssue(binding.issueId);
const agent = await getAgent(binding.agentId);
const ok = run.status === "running" && run.runtimeMode === "native"
&& issue.executionRunId === binding.runId && issue.assigneeAgentId === binding.agentId
&& !["paused", "terminated", "pending_approval", "error"].includes(agent.status);
if (!ok) throw new Error("skip: tool binding no longer authorized"); Type guard
function isBindingNotAuthorized(err: unknown): err is Error {
return err instanceof Error && err.message === "paperclip_runner_tool_binding_not_authorized";
} Try / catch
try {
return await listAuthorizedChatAttachments({ db, binding, limit });
} catch (err) {
if (err instanceof Error && err.message === "paperclip_runner_tool_binding_not_authorized") {
// stop tool activity for this run; do not retry until actor resumes or a new run binds the issue
return { status: "binding-revoked" as const };
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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'.
Common situations: 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.
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
- paperclip_runner_chat_attachment_principal_denied
- paperclip_runner_chat_attachment_read_scope_unavailable
- paperclip_runner_chat_attachment_source_denied
- paperclip_runner_file_handoff_not_authorized
- paperclip_runner_tool_binding_not_authorized
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/413fa07ac865ca2b.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/chat-attachment-reuse.ts:1054
eq(heartbeatRuns.id, input.binding.runId),
eq(heartbeatRuns.companyId, input.binding.companyId),
eq(heartbeatRuns.agentId, input.binding.agentId),
eq(heartbeatRuns.nativeIssueId, input.binding.issueId),
eq(heartbeatRuns.runtimeMode, "native"),
eq(heartbeatRuns.status, "running"),
eq(issues.assigneeAgentId, input.binding.agentId),
eq(issues.executionRunId, input.binding.runId),
),
)
.for("update")
.limit(1);
if (
!current ||
["paused", "terminated", "pending_approval", "error"].includes(
current.actorStatus,
)
) {
throw new Error("paperclip_runner_tool_binding_not_authorized");
}
const conversation = await authorizeChatConversationForBoundRun(
tx as unknown as Db,
input.binding,
current.run.contextSnapshot,
);
const sourceFilter = input.sourceCommentId ?? null;
const cursor = decodeListCursor(
input.cursor,
conversation.conversationId,
sourceFilter,
);
const rawCandidates = await tx.execute(sql<{
attachment_id: string;
source_comment_id: string;
created_at: Date | string;
}>`
with candidate_pairs as (View on GitHub (pinned to 3f1d897a7c)