paperclipai/paperclip · error
paperclip_runner_chat_attachment_storage_mismatch
paperclip_runner_chat_attachment_storage_mismatch
Error message
paperclip_runner_chat_attachment_storage_mismatch
What it means
After re-uploading the source bytes to the issue namespace via putStorageObjectWithin, the code verifies the storage write result (stored.byteSize, stored.sha256, stored.contentType) against the verified body and the source metadata. If any field disagrees, this error is thrown and the new storage object is not linked as an attachment. It ensures the storage layer actually persisted exactly the bytes it was given with the intended content type.
Solutions
- Fix the StorageService.putFile implementation to report byteSize/sha256/contentType computed from the exact bytes it persisted
- Pass contentType exactly as stored (avoid backends silently rewriting it); if the backend normalizes, propagate its returned value consistently
- In tests, make mock putFile echo the real input body's length/hash/contentType instead of hardcoded values
- Log the stored vs expected values on mismatch to identify which field diverges before retrying
Example fix
// before (mock storage returning canned metadata)
async putFile(input) { return { provider: "local", objectKey: key, byteSize: 0, sha256: "", contentType: "application/octet-stream", ... }; }
// after
async putFile(input) {
await write(key, input.body);
return { provider: "local", objectKey: key, byteSize: input.body.length, sha256: createHash("sha256").update(input.body).digest("hex").toLowerCase(), contentType: input.contentType, originalFilename: input.originalFilename };
} Defensive patterns
Strategy: validation
Validate before calling
const expectedSha = createHash("sha256").update(body).digest("hex").toLowerCase();
// after putFile returns `stored`:
if (stored.byteSize !== body.length || stored.sha256.toLowerCase() !== expectedSha || stored.contentType !== source.contentType)
throw new Error("storage write metadata diverges from written bytes"); Try / catch
try {
await prepareReusedChatAttachment({ db, binding, source, title });
} catch (err) {
if (err.message === "paperclip_runner_chat_attachment_storage_mismatch") {
// delete the orphaned stored object and retry with a corrected StorageService
await storage.deleteObject(binding.companyId, candidateObjectKey).catch(() => undefined);
} else throw err;
} Prevention
- Make StorageService.putFile return metadata computed from the bytes it actually persisted
- Avoid storage backends that rewrite or default content types; propagate the returned contentType
- Keep test/mocks faithful: echo real length/hash/contentType from the input body
- On mismatch, clean up the orphaned object key before retrying
When it happens
Trigger: storage.putFile returns metadata that disagrees with the write: byteSize != body.length, sha256 differs from input.source.sha256, or contentType differs from input.source.contentType — e.g. the storage service normalized/overrode the content type, a custom StorageService in tests returns canned metadata, or the putFile implementation reports pre-compression or multipart metrics.
Common situations: A custom or mock StorageService returning incorrect sha256/byteSize in its putFile result; a storage backend that rewrites content-type (e.g. defaults to application/octet-stream or appends charset); an adapter computing sha256 before transforming the body (compression/encryption) while metadata was compared against the raw source; case differences in contentType strings.
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_size_mismatch
- paperclip_runner_chat_attachment_read_integrity_mismatch
- paperclip_runner_chat_attachment_read_source_changed
- paperclip_runner_chat_attachment_source_integrity_mismatch
- paperclip_runner_file_handoff_storage_mismatch
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/676d3b487d77d4b0.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/chat-attachment-reuse.ts:1450
);
const stored = await putStorageObjectWithin(
storage,
{
companyId: input.binding.companyId,
namespace: `issues/${input.binding.issueId}`,
originalFilename: input.source.filename,
contentType: input.source.contentType,
body,
},
storageTimeoutMs,
);
try {
if (
stored.byteSize !== body.length ||
stored.sha256.toLowerCase() !== input.source.sha256.toLowerCase() ||
stored.contentType !== input.source.contentType
) {
throw new Error("paperclip_runner_chat_attachment_storage_mismatch");
}
const attachment = await issueService(input.db).createAttachment({
issueId: input.binding.issueId,
provider: stored.provider,
objectKey: stored.objectKey,
contentType: stored.contentType,
byteSize: stored.byteSize,
sha256: stored.sha256,
originalFilename: stored.originalFilename,
createdByAgentId: input.binding.agentId,
createdByRunId: input.binding.runId,
});
if (
!attachment.artifactWorkProductId ||
attachment.originatingRunId !== input.binding.runId
) {
throw new Error("paperclip_runner_chat_attachment_origin_not_persisted");
}View on GitHub (pinned to 3f1d897a7c)