{"record":{"id":"5c172af0a0022543","repo":"paperclipai/paperclip","slug":"paperclip-runner-chat-attachment-read-aborted","errorCode":"paperclip_runner_chat_attachment_read_aborted","errorMessage":"paperclip_runner_chat_attachment_read_aborted","messagePattern":"paperclip_runner_chat_attachment_read_aborted","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/services/native-runtime/chat-attachment-read.ts","lineNumber":223,"sourceCode":"      AbortSignal.timeout(this.options.storageTimeoutMs ?? 5_000),\n    ]);\n    let object: Awaited<ReturnType<StorageService[\"getObject\"]>> | undefined;\n    const aborted = new Promise<never>((_, reject) => {\n      signal.addEventListener(\n        \"abort\",\n        () => {\n          object?.stream.destroy();\n          reject(new Error(\"paperclip_runner_chat_attachment_read_aborted\"));\n        },\n        { once: true },\n      );\n    });\n    const acquiring = storage\n      .getObject(this.options.binding.companyId, source.objectKey)\n      .then((value) => {\n        if (signal.aborted) {\n          value.stream.destroy();\n          throw new Error(\"paperclip_runner_chat_attachment_read_aborted\");\n        }\n        object = value;\n        return value;\n      });\n    const read = (async () => {\n      const value = await acquiring;\n      const chunks: Buffer[] = [];\n      let length = 0;\n      try {\n        for await (const chunk of value.stream) {\n          const bytes = Buffer.from(chunk);\n          length += bytes.length;\n          if (length > source.byteSize || length > MAX_ATTACHMENT_BYTES)\n            throw new Error(\n              \"paperclip_runner_chat_attachment_read_size_mismatch\",\n            );\n          chunks.push(bytes);\n        }","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/chat-attachment-read.ts#L205-L241","documentation":"Storage fetches race against an abort signal combining scope closure with a storage timeout (default 5s). If the signal fires — scope closed or storage did not deliver the object in time — the object stream is destroyed and this error is thrown from #bytes via the aborted promise.","triggerScenarios":"Storage backend (S3/local) taking longer than storageTimeoutMs (default 5000ms) to return the object; scope.close() called while a read was streaming; AbortSignal.timeout firing on a slow or hung storage provider.","commonSituations":"Slow or degraded object storage; large attachments on high-latency links; network partitions; runs being torn down concurrently with reads.","solutions":["Increase storageTimeoutMs when reading large attachments or over slow links.","Check storage backend health/latency (S3 region, network path).","Retry the read once; transient storage slowness often resolves.","If scope closure causes it, stop issuing reads once the run is ending."],"exampleFix":"// before\nnew NativeChatAttachmentReadScope({ ..., storageTimeoutMs: undefined }); // 5s default\n// after\nnew NativeChatAttachmentReadScope({ ..., storageTimeoutMs: 30_000 });","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isAbortedError(e: unknown): boolean {\n  return e instanceof Error && e.message === \"paperclip_runner_chat_attachment_read_aborted\";\n}","tryCatchPattern":"try {\n  return await scope.read(input);\n} catch (e) {\n  if (isAbortedError(e)) {\n    await new Promise(r => setTimeout(r, 200));\n    return scope.read(input); // retry unless the scope was closed\n  }\n  throw e;\n}","preventionTips":["Raise storageTimeoutMs for large attachments or slow storage.","Monitor storage backend latency and error rates.","Stop issuing reads once close() has been requested.","Prefer local/fast storage for the run workspace path."],"tags":["timeout","abort","storage","network"],"backgroundTag":"request-timeout","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}