paperclipai/paperclip · error

paperclip_runner_chat_attachment_source_size_mismatch

paperclip_runner_chat_attachment_source_size_mismatch

Error message

paperclip_runner_chat_attachment_source_size_mismatch

What it means

This error is thrown by readSourceBytes in chat-attachment-reuse.ts while streaming a source attachment object from storage before re-binding it to a chat/native run. As bytes are read, a running total is compared against the declared source.byteSize and the hard cap MAX_ATTACHMENT_BYTES; the moment the streamed total exceeds either limit, the stream is destroyed and this error is thrown. It exists to prevent trusting a storage object whose actual size diverges from its declared metadata (or an oversized attachment) from being silently reused.

Solutions

  1. Verify source.byteSize matches the actual stored object (head the object / stat the file) and fix or regenerate the source record
  2. Re-upload the source attachment so byteSize and content are consistent, then retry prepareReusedChatAttachment
  3. Check whether anything wrote to the same objectKey after the source record was created (key collisions across namespaces); use a unique key
  4. Confirm the object is within MAX_ATTACHMENT_BYTES; if larger, reject it upstream before attempting reuse

Example fix

// before (caller passes stale metadata)
await prepareReusedChatAttachment({ db, binding, source: { ...src, byteSize: 1024 }, title });
// after (re-read actual size before reuse)
const head = await storage.headObject(binding.companyId, src.objectKey);
await prepareReusedChatAttachment({ db, binding, source: { ...src, byteSize: head.byteSize }, title });
Defensive patterns

Strategy: validation

Validate before calling

const head = await storage.headObject(companyId, source.objectKey);
if (head.byteSize !== source.byteSize) throw new Error("source byteSize stale; refresh source record");
if (head.byteSize > MAX_ATTACHMENT_BYTES) throw new Error("source object exceeds attachment cap");

Type guard

function isReusableSource(s) {
  return typeof s.byteSize === "number" && Number.isFinite(s.byteSize) && s.byteSize > 0 && s.byteSize <= MAX_ATTACHMENT_BYTES && typeof s.sha256 === "string" && /^[0-9a-f]{64}$/.test(s.sha256);
}

Prevention

When it happens

Trigger: Calling prepareReusedChatAttachment where storage.getObject(companyId, source.objectKey) returns a stream longer than source.byteSize (stale/mutated object under the same key), or where the object exceeds MAX_ATTACHMENT_BYTES (e.g. a metadata record with a wrong/undersized byteSize field, or an object overwritten with larger content after the source record was created).

Common situations: A storage backend (S3/local) object was re-uploaded or appended after the attachment source record was snapshotted; the source record's byteSize was computed at a different point than the object write (partial write followed by full write); a caller hand-crafted a ChatAttachmentReuseSource with an incorrect byteSize; or an oversized file slipped into storage bypassing upload validation.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/native-runtime/chat-attachment-reuse.ts:1302

    object = await Promise.race([objectPromise, acquisitionTimeout]);
  } finally {
    clearTimeout(acquisitionTimer);
  }
  const timeout = setTimeout(() => {
    object.stream.destroy(
      new Error("paperclip_runner_chat_attachment_source_read_timed_out"),
    );
  }, timeoutMs);
  timeout.unref?.();
  const chunks: Buffer[] = [];
  let total = 0;
  try {
    for await (const chunk of object.stream) {
      const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      total += buffer.length;
      if (total > source.byteSize || total > MAX_ATTACHMENT_BYTES) {
        object.stream.destroy();
        throw new Error(
          "paperclip_runner_chat_attachment_source_size_mismatch",
        );
      }
      chunks.push(buffer);
    }
  } finally {
    clearTimeout(timeout);
  }
  const body = Buffer.concat(chunks);
  if (
    body.length !== source.byteSize ||
    createHash("sha256").update(body).digest("hex") !==
      source.sha256.toLowerCase()
  ) {
    if (!object.stream.destroyed) {
      object.stream.destroy();
    }
    throw new Error(

View on GitHub (pinned to 3f1d897a7c)