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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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


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)