paperclipai/paperclip · error · Error

paperclip_runner_chat_attachment_read_scope_unavailable

paperclip_runner_chat_attachment_read_scope_unavailable

Error message

paperclip_runner_chat_attachment_read_scope_unavailable

What it means

The runner's read_chat_attachment tool requires a chatAttachmentReadScope bound to the exact run identity. This error is thrown when the scope is missing entirely, or its bound identity (companyId, issueId, runId, agentId) does not match the current tool-execution binding — meaning the tool has no authorized channel to read chat attachments right now.

Solutions

  1. Ensure chatAttachmentReadScope is provided when constructing the runner binding for runs that may call the attachment tool.
  2. Recreate the runner/binding so the scope's identity matches the current companyId/issueId/runId/agentId.
  3. Restart the native run so a fresh scope is bound to the current run identity.
  4. Check whether the binding was mutated (issue reassignment, run restart) after scope creation and re-bind accordingly.

Example fix

// before
const runner = createNativeRunner({ binding: { agentId, issueId, runId, companyId, ... } }); // no chatAttachmentReadScope
// after
const runner = createNativeRunner({ binding: { agentId, issueId, runId, companyId, chatAttachmentReadScope: createChatAttachmentReadScope({ binding: { agentId, issueId, runId, companyId } }), ... } });
Defensive patterns

Strategy: type-guard

Validate before calling

function hasMatchingScope(binding) {
  const scope = binding.chatAttachmentReadScope;
  if (!scope) return false;
  return ["companyId", "issueId", "runId", "agentId"].every(k => scope.options.binding[k] === binding[k]);
}

Type guard

const scopeIsCurrent = (binding) => !!binding.chatAttachmentReadScope && ["companyId","issueId","runId","agentId"].every(k => binding.chatAttachmentReadScope.options.binding[k] === binding[k]);

Try / catch

try {
  return await authority.execute(call);
} catch (e) {
  if (e.message === "paperclip_runner_chat_attachment_read_scope_unavailable") {
    return respawnRunnerWithFreshScope(binding); // restart run with a scope bound to current identity
  }
  throw e;
}

Prevention

When it happens

Trigger: execute() handles a call with call.tool === READ_CHAT_ATTACHMENT_TOOL_NAME and either this.binding.chatAttachmentReadScope is undefined/null, or any of scope.options.binding.companyId/issueId/runId/agentId !== the corresponding this.binding value.

Common situations: A runner was constructed without the attachment-read scope wired in (scope not provided at runner startup); a stale runner instance from a previous run/agent tries to read attachments after respawn; test or replay harness invoking the tool without the scope; binding fields changed (e.g., issue reassigned) after scope creation.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/e451138972b2cf68. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/native-runtime/paperclip-runner-tool-authority.ts:296

    ) {
      throw new Error("paperclip_runner_tool_not_advertised");
    }
    if (
      !runnerApiToolsEnabled(
        this.binding.companyId,
        this.binding.apiToolsEnabled,
      ) &&
      ["search_api", "call_api"].includes(call.tool)
    ) {
      throw new Error("paperclip_runner_tool_not_advertised");
    }
    const context = await this.#boundContext();
    const input = record(call.arguments);
    if (call.tool === READ_CHAT_ATTACHMENT_TOOL_NAME) {
      const scope = this.binding.chatAttachmentReadScope;
      const identityKeys = ["companyId", "issueId", "runId", "agentId"] as const;
      if (!scope || identityKeys.some((key) => scope.options.binding[key] !== this.binding[key])) {
        throw new Error("paperclip_runner_chat_attachment_read_scope_unavailable");
      }
      if (Object.keys(input).some((key) => key !== "sourceCommentId" && key !== "attachmentId")) {
        throw new Error("paperclip_runner_chat_attachment_read_arguments_invalid");
      }
      return scope.read({
        sourceCommentId: requiredUuid(input.sourceCommentId),
        attachmentId: requiredUuid(input.attachmentId),
      });
    }
    if (call.tool === READ_CURRENT_WAKE_COMMENTS_TOOL_NAME) {
      if (!this.binding.currentWakeComments) {
        throw new Error("paperclip_runner_tool_not_advertised");
      }
      return readCurrentWakeComments(
        this.db,
        this.binding.currentWakeComments,
        input,
      );

View on GitHub (pinned to 3f1d897a7c)