paperclipai/paperclip · error · Error
Public viewer asset differs from trusted build: ${file}
Error message
Public viewer asset differs from trusted build: ${file} What it means
Files under viewer/ are compared byte-for-byte against a trusted viewer build (trustedViewerFiles). If a viewer file is missing from the trusted build or its bytes differ, the script aborts naming the file, ensuring only known-good, unmodified viewer assets are published.
Source
Thrown at packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs:171
);
}
for (const file of files) {
if (!isPublicProtocolEvalPath(file)) {
throw new Error(
`Refusing non-allowlisted public protocol eval path ${file}`,
);
}
const absolute = resolve(root, ...file.split("/"));
const metadata = await stat(absolute);
if (metadata.size === 0 || metadata.size > 12 * 1024 * 1024) {
throw new Error(
`Public protocol eval file exceeds its size boundary: ${file}`,
);
}
if (file.startsWith("viewer/")) {
const expected = viewer?.files.get(file);
if (!expected || !expected.equals(await readFile(absolute)))
throw new Error(
`Public viewer asset differs from trusted build: ${file}`,
);
continue;
}
const content = await readFile(absolute, "utf8");
const richAttempt = /^attempts\/[^/]+\/index\.html$/.test(file);
const payload = richAttempt
? validatePublicViewerPage(content, viewer.index)
: null;
if (!richAttempt) {
for (const pattern of CREDENTIAL_PATTERNS) {
if (pattern.test(content))
throw new Error(
`Public report contains credential/session material: ${file}`,
);
}
if (extname(file) !== ".html") continue;
for (const pattern of ACTIVE_HTML_PATTERNS) {View on GitHub (pinned to 01ad858492)
Solutions
- Re-copy or rebuild the viewer assets from the same trusted viewerRoot the script compares against, then re-run.
- Ensure no post-processing step modifies files under viewer/ after they are placed in the report.
- Clear the report's viewer/ directory and regenerate so it exactly matches the trusted build.
Example fix
# before: stale/edited viewer asset public-report/viewer/app.js (hand-edited) # after $ rm -rf public-report/viewer && cp -R trusted-viewer/dist public-report/viewer $ node scripts/publish-runner-protocol-eval-history.mjs ...
Defensive patterns
Strategy: validation
Validate before calling
import { createHash } from 'node:crypto';
import { readdirSync, readFileSync } from 'node:fs';
for (const rel of listViewerFiles(reportRoot)) {
const trustedPath = `${trustedViewerRoot}/${rel.slice('viewer/'.length)}`;
const a = createHash('sha256').update(readFileSync(`${reportRoot}/${rel}`)).digest('hex');
const b = createHash('sha256').update(readFileSync(trustedPath)).digest('hex');
if (a !== b) throw new Error(`viewer asset differs from trusted build: ${rel}`);
} Try / catch
try {
await publishReport(root);
} catch (err) {
if (String(err.message).startsWith('Public viewer asset differs from trusted build')) {
console.error('Restore viewer/ from the trusted build before publishing:', err.message);
} else throw err;
} Prevention
- Copy viewer assets from the trusted build as the last step, then publish immediately with no intermediate mutation steps.
- Never hand-edit or post-process files under viewer/ in the report directory.
- Use the same viewer build (same viewerRoot) for generating and validating the report.
When it happens
Trigger: Publishing when a viewer/ file was modified after the trusted build (post-processing, minifier rerun, manual edit) or when the viewer/ directory content does not match the trusted viewerRoot used for comparison.
Common situations: Rebuilding the viewer with different tooling/settings than the trusted build; a script rewrote viewer asset hashes; stale viewer/ files from a previous build mixed with new ones; pointing the script at a different viewer build than the one copied into the report.
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
- Materialized OpenCode executable digest mismatch
- ACPX ${agent} runtime executable digest mismatch
- ACPX snapshot manifest digest mismatch
- ACPX private snapshot digest mismatch
- runnerd digest mismatch: expected ${request.runnerd.sha256},
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/95b861a8de49c9e1.
Report an issue: GitHub.