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
- Verify source.byteSize matches the actual stored object (head the object / stat the file) and fix or regenerate the source record
- Re-upload the source attachment so byteSize and content are consistent, then retry prepareReusedChatAttachment
- Check whether anything wrote to the same objectKey after the source record was created (key collisions across namespaces); use a unique key
- 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
- Always derive byteSize from the same write that stored the object, never from a stale snapshot
- Use write-once object keys so an object cannot grow after its metadata is recorded
- Validate attachment size against MAX_ATTACHMENT_BYTES at upload time
- Treat any source-size mismatch as evidence of a metadata/storage divergence and regenerate the source record
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
- paperclip_runner_chat_attachment_storage_mismatch
- Attachment exceeds the configured size limit
- Image exceeds attachment bound
- paperclip_runner_chat_attachment_read_integrity_mismatch
- paperclip_runner_chat_attachment_read_size_mismatch
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)