paperclipai/paperclip · error · Error
paperclip_runner_file_handoff_storage_mismatch
paperclip_runner_file_handoff_storage_mismatch
Error message
paperclip_runner_file_handoff_storage_mismatch
What it means
After writing the deliverable to storage, prepareNativeRunnerFileHandoff verifies that the StorageService's putFile result actually matches what was requested: byte size, lowercase hex sha256, and content type must equal the verified workspace file. A mismatch means the storage backend corrupted, transformed, or mis-reported the object, so the attachment is not created and the uploaded object is cleaned up.
Solutions
- Fix the StorageService.putFile implementation to echo back the exact contentType and byteSize of the written body and compute sha256 over the exact bytes written, lowercased.
- Verify the storage backend is not transcoding/compressing content; disable content-type normalization or set it explicitly on upload.
- Check stored object integrity directly (download and hash it) to distinguish a metadata bug from real corruption.
- If a custom storage wrapper lowercases/uppercases hashes inconsistently, normalize both sides with .toLowerCase() before comparison.
Example fix
// before (custom storage service)
return { byteSize: Buffer.byteLength(body).valueOf(), sha256: hash.toUpperCase(), contentType: 'application/octet-stream' };
// after
return { byteSize: body.length, sha256: createHash('sha256').update(body).digest('hex'), contentType: requestedContentType }; Defensive patterns
Strategy: validation
Validate before calling
// verify storage round-trip before handing the result to the handoff flow
const stored = await storage.putFile(req);
const ok = stored.byteSize === req.body.length
&& stored.sha256.toLowerCase() === expectedSha256
&& stored.contentType === req.contentType;
if (!ok) {
await storage.deleteObject(req.companyId, stored.objectKey).catch(() => undefined);
throw new Error('storage returned metadata that mismatches the uploaded body');
} Try / catch
try {
await prepareNativeRunnerFileHandoff(input);
} catch (e) {
if (e.message === 'paperclip_runner_file_handoff_storage_mismatch') {
// the object is auto-deleted by the catch path; inspect/fix StorageService then retry
await auditStorageService();
return retryHandoffWithDirectVerification(input);
}
throw e;
} Prevention
- Write unit tests for custom StorageService implementations asserting byteSize/sha256/contentType echo the input exactly.
- Always lowercase sha256 in storage implementations.
- Disable backend content-type normalization/sniffing for issue attachment buckets.
- Monitor storage metadata mismatches to catch transcoding proxies early.
When it happens
Trigger: storage.putFile returns metadata that disagrees with the input body: byteSize !== body.length, sha256 (case-insensitively) differs, or contentType differs from the verified deliverable's content type — e.g. a storage layer that recomputes/mangles content type, applies compression/transcoding, or returns stale metadata.
Common situations: Custom StorageService implementation (S3/local/fs) that reports rounded sizes or normalizes content types; a proxy/middleware altering uploads; case-sensitive sha256 comparison hitting a backend returning uppercase hex without toLowerCase in a custom build; storage plugin bug or misconfigured content-type sniffing.
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_read_integrity_mismatch
- paperclip_runner_chat_attachment_source_integrity_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/5119009a3a2cace9.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/native-runner-file-handoff.ts:1243
rollbackDefinitePreCommitFailure: null,
};
}
const storage = input.storage ?? getStorageService();
const stored = await storage.putFile({
companyId: input.binding.companyId,
namespace: `issues/${input.binding.issueId}`,
originalFilename: verified.filename,
contentType: verified.contentType,
body: verified.body,
});
try {
if (
stored.byteSize !== verified.body.length ||
stored.sha256.toLowerCase() !== verified.sha256 ||
stored.contentType !== verified.contentType
) {
throw new Error("paperclip_runner_file_handoff_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.originatingRunId !== input.binding.runId ||
!attachment.artifactWorkProductId
) {
throw new Error("paperclip_runner_file_handoff_origin_not_persisted");
}View on GitHub (pinned to 3f1d897a7c)