paperclipai/paperclip · error
paperclip_runner_chat_attachment_read_scope_closed
paperclip_runner_chat_attachment_read_scope_closed
Error message
paperclip_runner_chat_attachment_read_scope_closed
What it means
NativeChatAttachmentReadScope guards all operations with a lifetime check: once close() has been called on the scope, every entry point (read, bytes, authorization) throws this error. The scope is server-owned and tied to a run's lifetime; closing aborts in-flight reads and cleans up staged workspace files. The error exists so a tool call racing with scope teardown fails loudly instead of publishing a workspace path after cleanup has started.
Solutions
- Do not call read() after awaiting scope.close(); treat the scope as single-run-lifetime.
- Catch this error in the tool dispatcher and surface it as a benign 'run ended' tool failure, not a bug.
- If reads must outlive the current run, create a new scope with a new binding rather than reusing a closed one.
- Check whether run teardown was triggered prematurely (timeout, abort) while the agent was still working.
Example fix
// before
await scope.close();
const file = await scope.read({ sourceCommentId, attachmentId }); // throws
// after
if (!isScopeClosed) {
const file = await scope.read({ sourceCommentId, attachmentId });
await scope.close();
} Defensive patterns
Strategy: try-catch
Validate before calling
let scopeOpen = true;
async function closeScope(s) { scopeOpen = false; await s.close(); } Type guard
function isScopeClosedError(e: unknown): boolean {
return e instanceof Error && e.message === "paperclip_runner_chat_attachment_read_scope_closed";
} Try / catch
try {
const file = await scope.read(input);
} catch (e) {
if (isScopeClosedError(e)) return { status: "run_ended" };
throw e;
} Prevention
- Await scope.close() only after all tool activity for the run has finished.
- Track scope lifetime alongside run lifecycle events.
- Never cache the scope object across runs.
- Treat scope-closed tool failures as expected teardown races, not defects.
When it happens
Trigger: Calling read() after scope.close() (or while close() is in progress); an async continuation (authorization retry, post-storage staging) resuming after close() aborted the internal AbortController; the runner invoking the read_chat_attachment tool after the run has already ended.
Common situations: Run completion/timeout races a slow attachment read; a retry loop re-invokes a tool with a stale scope object; tests replaying tool calls after teardown.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ACPX provider ownership admission is already active
- capability_live_attempt_not_running
- Capability live turn admission was abandoned during teardown
- Chat SDK endpoint runtime was retired
- Chat SDK runtime is shutting down
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/6981725f5fe4123f.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/chat-attachment-read.ts:65
#pending = new Set<Promise<unknown>>();
#cleanups: Array<() => Promise<void>> = [];
#abort = new AbortController();
#closing: Promise<void> | null = null;
constructor(
readonly options: {
db: Db;
binding: ChatReuseBinding;
workspaceRoot: string;
executionTargetKind: "local" | "remote";
storage?: StorageService;
storageTimeoutMs?: number;
},
) {}
#assertOpen() {
if (this.#closed)
throw new Error("paperclip_runner_chat_attachment_read_scope_closed");
}
read(input: { sourceCommentId: string; attachmentId: string }) {
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",View on GitHub (pinned to 3f1d897a7c)