paperclipai/paperclip · error
paperclip_runner_chat_attachment_read_integrity_mismatch
paperclip_runner_chat_attachment_read_integrity_mismatch
Error message
paperclip_runner_chat_attachment_read_integrity_mismatch
What it means
After fully reading the stream, #bytes verifies the total length equals source.byteSize and that the SHA-256 of the body matches source.sha256. Any mismatch means the bytes delivered by storage do not correspond to the authorized attachment, so the content is discarded and this error is thrown. This is the final content-integrity gate before staging.
Solutions
- Re-upload the attachment so bytes and sha256/byteSize metadata agree, then retry the read.
- Check the storage backend for truncation/corruption (multipart completeness, checksums).
- Fix any writer that mutates object bytes without updating the recorded sha256.
- Retry once to rule out a transient truncated transfer.
Example fix
// before
await replaceObject(key, newBytes); // metadata sha256 now stale
// after
await replaceObject(key, newBytes, { sha256: sha256(newBytes), byteSize: newBytes.byteLength }); Defensive patterns
Strategy: try-catch
Type guard
function isIntegrityError(e: unknown): boolean {
return e instanceof Error && e.message === "paperclip_runner_chat_attachment_read_integrity_mismatch";
} Try / catch
try {
return await scope.read(input);
} catch (e) {
if (isIntegrityError(e)) {
return { status: "integrity_failure" }; // re-upload the attachment; never stage unverified bytes
}
throw e;
} Prevention
- Store sha256 alongside every uploaded object and verify after upload.
- Use checksum-validated storage uploads (Content-MD5 / x-amz-checksum).
- Never mutate object bytes without updating sha256/byteSize metadata.
- Retry once for transient truncation, then flag the object as corrupt.
When it happens
Trigger: Storage returns truncated or corrupted bytes (short read, interrupted transfer); object content was mutated without updating metadata; hash-case mismatch after a buggy writer stored an uppercase hash; partial upload committed with stale sha256 metadata.
Common situations: Flaky storage backends dropping stream tails; bit-rot or manual object replacement in the bucket; re-encryption pipelines changing bytes but not metadata.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- paperclip_runner_chat_attachment_source_integrity_mismatch
- paperclip_runner_file_handoff_storage_mismatch
- ACPX runtime executable digest mismatch
- ACPX private snapshot digest mismatch
- ACPX snapshot manifest digest mismatch
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/78ef6a720f6def59.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/chat-attachment-read.ts:251
for await (const chunk of value.stream) {
const bytes = Buffer.from(chunk);
length += bytes.length;
if (length > source.byteSize || length > MAX_ATTACHMENT_BYTES)
throw new Error(
"paperclip_runner_chat_attachment_read_size_mismatch",
);
chunks.push(bytes);
}
} finally {
value.stream.destroy();
}
const body = Buffer.concat(chunks);
if (
length !== source.byteSize ||
createHash("sha256").update(body).digest("hex") !==
source.sha256.toLowerCase()
)
throw new Error(
"paperclip_runner_chat_attachment_read_integrity_mismatch",
);
return body;
})();
return Promise.race([read, aborted]);
}
close(): Promise<void> {
if (this.#closing) return this.#closing;
this.#closed = true;
this.#abort.abort();
this.#closing = (async () => {
await Promise.allSettled([...this.#pending]);
const results = await Promise.allSettled(
this.#cleanups.map((cleanup) => cleanup()),
);
if (results.some((result) => result.status === "rejected"))
throw new Error("paperclip_runner_chat_attachment_read_cleanup_failed");View on GitHub (pinned to 3f1d897a7c)