stablyai/orca · error

packaged plugin contains a symlink: ${relative(root, entryPa

Error message

packaged plugin contains a symlink: ${relative(root, entryPath)}

What it means

Thrown by the packaged plugin resource verifier when lstatSync detects a symbolicic link during the recursive tree walk. Packaged plugins must contain only real files and directories — symlinks break content-addressable hashing (they produce platform-dependent behavior), cause issues on Windows where symlinks may not be supported, and can escape the plugin root. The check uses lstatSync (not statSync) specifically to detect the link itself rather than its target.

Source

Thrown at config/scripts/verify-packaged-plugin-resources.cjs:33

  const files = []
  let entriesVisited = 0
  let totalBytes = 0
  const visit = (directory) => {
    const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
      left.name < right.name ? -1 : left.name > right.name ? 1 : 0
    )
    for (const entry of entries) {
      if (directory === root && entry.name === '.git') {
        continue
      }
      const entryPath = join(directory, entry.name)
      const metadata = lstatSync(entryPath)
      entriesVisited += 1
      if (entriesVisited > MAX_PLUGIN_FILES) {
        throw new Error(`plugin exceeds the ${MAX_PLUGIN_FILES}-entry limit`)
      }
      if (metadata.isSymbolicLink()) {
        throw new Error(`packaged plugin contains a symlink: ${relative(root, entryPath)}`)
      }
      if (metadata.isDirectory()) {
        visit(entryPath)
      } else if (metadata.isFile()) {
        totalBytes += metadata.size
        if (totalBytes > MAX_PLUGIN_TOTAL_BYTES) {
          throw new Error(`plugin exceeds the ${MAX_PLUGIN_TOTAL_BYTES}-byte limit`)
        }
        files.push({ path: entryPath, size: metadata.size })
      } else {
        throw new Error(`packaged plugin contains an unsupported entry: ${entryPath}`)
      }
    }
  }
  visit(root)
  const hash = createHash('sha256').update('orca-plugin-tree-v1\0')
  for (const file of files) {
    const relativePath = relative(root, file.path).replaceAll('\\', '/')

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Identify the symlink from the error message (it includes the relative path): ls -la <pluginRoot>/<relativePath>.
  2. Replace the symlink with a real copy of the target file or directory.
  3. Ensure the packaging step resolves all symlinks before creating the plugin archive: cp -rL or rsync -L.
  4. If using pnpm, run the packaging step after pnpm install --shamefully-hoist or use a bundler that produces a self-contained output.
Defensive patterns

Strategy: validation

Validate before calling

// Before packaging, scan for symlinks in the plugin directory.
const { execSync } = require('node:child_process')

function preCheckPluginSymlinks(pluginRoot) {
  try {
    const output = execSync(`find ${JSON.stringify(pluginRoot)} -type l`, {
      encoding: 'utf8'
    }).trim()
    return { ok: output.length === 0, symlinks: output.split('\n').filter(Boolean) }
  } catch {
    return { ok: true, symlinks: [] }
  }
}

Prevention

When it happens

Trigger: hashPackagedPluginTree(root) encounters an entry where metadata.isSymbolicLink() returns true. Common in pnpm/yarn-workspace monorepos where node_modules use symlinks for package linking, or when a developer creates convenience symlinks within a plugin directory that get packaged.

Common situations: The plugin was developed in a pnpm workspace where dependencies are symlinked; a developer created a symlink for convenience (e.g., linking to a shared config); the packaging step dereferences most symlinks but missed some; cross-platform builds where symlinks created on Linux are packaged into a Windows-incompatible layout.

Related errors


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