paperclipai/paperclip · error
paperclip_runner_chat_attachment_read_limit
paperclip_runner_chat_attachment_read_limit
Error message
paperclip_runner_chat_attachment_read_limit
What it means
Each scope allows at most 20 attachment reads; a per-scope counter increments on every read() call and this error is thrown once the 21st is attempted. The cap bounds storage traffic and staged-file cleanup work per run, preventing an agent loop from hammering attachments indefinitely.
Solutions
- Have the agent read only the attachments it actually needs per turn; metadata from list_chat_attachments usually suffices for filtering.
- Open a fresh scope (new run/binding) if more than 20 genuine reads are required.
- Detect this error and stop retrying; it is deterministic, not transient.
- Cache previously read attachment results instead of re-reading the same ids.
Example fix
// before for (const att of allAttachments) await scope.read(att); // >20 throws // after const subset = allAttachments.slice(0, 20); // or per-turn selection for (const att of subset) await scope.read(att);
Defensive patterns
Strategy: try-catch
Validate before calling
if (scope.readCountEstimate >= 20) throw new Error("attachment read budget exhausted for this run"); Try / catch
try {
const file = await scope.read(input);
} catch (e) {
if (e.message === "paperclip_runner_chat_attachment_read_limit") {
return { status: "read_budget_exhausted" }; // do not retry
}
throw e;
} Prevention
- Budget reads per run; prefer metadata for triage and read only chosen files.
- Cache read results by (sourceCommentId, attachmentId).
- Break read loops when the same ids repeat.
- Start a new run/scope if a workflow legitimately needs >20 reads.
When it happens
Trigger: An agent repeatedly calling read_chat_attachment more than 20 times within one run/scope, e.g. a retry loop or a model re-reading the same attachments every turn of a long conversation.
Common situations: Loopy agent behavior re-reading files; broad batch scripts reading every attachment in a chat; long multi-turn sessions sharing one scope.
Related errors
- quota
- Anthropic Managed Agents request failed with HTTP
- dropping batch after attempt(s); event(s) lost
- github_webhook_recovery_http
- quota
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/1f01593cd22f273c.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/chat-attachment-read.ts:87
this.#assertOpen();
if (
!UUID.test(input.sourceCommentId) ||
!UUID.test(input.attachmentId) ||
Object.keys(input).some(
(key) => key !== "sourceCommentId" && key !== "attachmentId",
)
) {
throw new Error(
"paperclip_runner_chat_attachment_read_arguments_invalid",
);
}
if (this.options.executionTargetKind !== "local") {
throw new Error(
"paperclip_runner_chat_attachment_remote_staging_unsupported",
);
}
if (++this.#readCount > 20)
throw new Error("paperclip_runner_chat_attachment_read_limit");
const pending = this.#read(input);
this.#pending.add(pending);
void pending
.finally(() => this.#pending.delete(pending))
.catch(() => undefined);
return pending;
}
async #authorized(input: {
sourceCommentId: string;
attachmentId: string;
}): Promise<ChatAttachmentReuseSource> {
// Run-event persistence also briefly locks heartbeat_runs. A NOWAIT miss
// is not evidence of policy revocation: retry the whole authorization in
// a fresh transaction, never hold partial locks while backing off.
const deadline = Date.now() + 1_000;
for (;;) {
this.#assertOpen();View on GitHub (pinned to 3f1d897a7c)