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
- Ensure chatAttachmentReadScope is provided when constructing the runner binding for runs that may call the attachment tool.
- Recreate the runner/binding so the scope's identity matches the current companyId/issueId/runId/agentId.
- Restart the native run so a fresh scope is bound to the current run identity.
- 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
- Always construct runner bindings for attachment-capable runs with chatAttachmentReadScope attached.
- Rebuild the scope whenever the run identity changes (restart, reassign, respawn).
- Assert scope-binding identity equality in runner startup tests.
- Never share runner instances across runs or agents.
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
- paperclip_runner_chat_attachment_principal_denied
- paperclip_runner_chat_attachment_source_denied
- paperclip_runner_file_handoff_not_authorized
- paperclip_runner_tool_binding_not_authorized
- paperclip_runner_tool_binding_not_authorized
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)