stablyai/orca · critical
[verify-packaged-plugin-resources] packaged bytes do not mat
Error message
[verify-packaged-plugin-resources] packaged bytes do not match ${entry.pluginKey} What it means
Thrown by the packaged plugin resource verifier when the SHA-256 content hash computed from the actual plugin directory tree does not match the contentHash recorded in bundled-plugins.json. The hash (hashPackagedPluginTree) is a deterministic hash over the sorted file list: for each file it incorporates the relative path length, path bytes, file size, and file content. A mismatch means the packaged bytes differ from what was indexed — indicating corruption, stale index, or tampering.
Source
Thrown at config/scripts/verify-packaged-plugin-resources.cjs:101
typeof entry?.pluginKey !== 'string' ||
typeof entry.path !== 'string' ||
!/^[0-9a-f]{64}$/.test(entry.contentHash)
) {
throw new Error('[verify-packaged-plugin-resources] bundled plugin entry is invalid')
}
const pluginRoot = resolve(launchRoot, entry.path)
const fromRoot = relative(resolvedRoot, pluginRoot)
if (!fromRoot || fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
throw new Error('[verify-packaged-plugin-resources] bundled plugin path escapes launch root')
}
const manifest = readJsonFile(join(pluginRoot, 'orca-plugin.json'), 'plugin manifest')
if (`${manifest.publisher}.${manifest.id}` !== entry.pluginKey) {
throw new Error(
`[verify-packaged-plugin-resources] manifest identity does not match ${entry.pluginKey}`
)
}
if (hashPackagedPluginTree(pluginRoot) !== entry.contentHash) {
throw new Error(
`[verify-packaged-plugin-resources] packaged bytes do not match ${entry.pluginKey}`
)
}
}
console.log(
`[verify-packaged-plugin-resources] OK — verified ${index.plugins.length} bundled plugin(s)`
)
}
module.exports = { verifyPackagedPluginResources }
View on GitHub (pinned to 1136503c6a)
Solutions
- Re-run the plugin indexing/build step to regenerate bundled-plugins.json with hashes that match the current packaged bytes.
- Ensure the indexing step runs AFTER all build/minification/processing steps so the hash reflects final packaged content.
- Check for line-ending normalization issues: ensure git's autocrlf and the build pipeline produce consistent line endings.
- Verify the hashing algorithm in the indexing script matches hashPackagedPluginTree — same sort order, same framing (path length as BigUInt64BE, then path, then size as BigUInt64BE, then content).
- If the plugin content is intentionally different from the indexed version, the index must be regenerated — never manually edit the contentHash.
Defensive patterns
Strategy: validation
Validate before calling
// Before packaging, verify the hash matches by computing it the same way.
const { createHash } = require('node:crypto')
const { lstatSync, readFileSync, readdirSync } = require('node:fs')
const { join, relative } = require('node:path')
function computePluginTreeHash(root) {
// Must match hashPackagedPluginTree exactly — same framing, sort, and content.
const files = []
const visit = (dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
)) {
const p = join(dir, entry.name)
const meta = lstatSync(p)
if (meta.isDirectory()) visit(p)
else if (meta.isFile()) files.push({ path: p, size: meta.size })
}
}
visit(root)
const hash = createHash('sha256').update('orca-plugin-tree-v1\0')
for (const f of files) {
const rel = relative(root, f.path).replaceAll('\\', '/')
const lenBuf = Buffer.allocUnsafe(8)
lenBuf.writeBigUInt64BE(BigInt(Buffer.byteLength(rel, 'utf8')))
hash.update(lenBuf).update(rel, 'utf8')
const sizeBuf = Buffer.allocUnsafe(8)
sizeBuf.writeBigUInt64BE(BigInt(f.size))
hash.update(sizeBuf).update(readFileSync(f.path))
}
return hash.digest('hex')
} Prevention
- Always run the indexing step AFTER all build, minification, and post-processing steps so hashes reflect final packaged content.
- Normalize line endings (LF) in the build pipeline to prevent cross-platform hash mismatches.
- Keep the hashing algorithm in the indexing script and in hashPackagedPluginTree perfectly synchronized — any framing change invalidates all existing hashes.
- Never edit plugin content after indexing without re-running the indexer.
When it happens
Trigger: hashPackagedPluginTree(pluginRoot) returns a different hash than entry.contentHash. This happens when files in the plugin directory were added, removed, modified, or renamed after the index was generated; when the hashing algorithm changed; or when packaging produced different content (e.g., different line endings, minification settings).
Common situations: The plugin source changed but the index was not regenerated; a build step modified files after indexing (e.g., post-index minification, source map generation); cross-platform line ending differences (CRLF vs LF) altered file bytes; a different Node version's Buffer behavior changed hash input; the indexing script's hash algorithm was updated without re-indexing existing plugins.
Related errors
- [verify-packaged-plugin-resources] bundled plugin entry is i
- plugin exceeds the ${MAX_PLUGIN_FILES}-entry limit
- packaged plugin contains a symlink: ${relative(root, entryPa
- plugin exceeds the ${MAX_PLUGIN_TOTAL_BYTES}-byte limit
- packaged plugin contains an unsupported entry: ${entryPath}
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/b6699c25f6504d47.
Report an issue: GitHub.