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

  1. Re-run the plugin indexing/build step to regenerate bundled-plugins.json with hashes that match the current packaged bytes.
  2. Ensure the indexing step runs AFTER all build/minification/processing steps so the hash reflects final packaged content.
  3. Check for line-ending normalization issues: ensure git's autocrlf and the build pipeline produce consistent line endings.
  4. 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).
  5. 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

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


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/b6699c25f6504d47. Report an issue: GitHub.