stablyai/orca · warning · RuntimeUploadSymlinkError

Symlink not allowed in '${normalizeRelativeUploadPath(relati

Error message

Symlink not allowed in '${normalizeRelativeUploadPath(relative(rootPath, dirPath))}'

What it means

Thrown as RuntimeUploadSymlinkError by stageDirectoryEntries() when a visited directory path itself lstat()s as a symbolic link. stageDirectoryEntries is part of the runtime-upload staging path (reading files to base64 for remote/agent upload); symlinks are forbidden to prevent traversal and non-portable link semantics. The dedicated error class is caught one frame up in stageOneSourceForRuntimeUpload and converted into a { status: 'skipped', reason: 'symlink' } result, so the whole source is skipped rather than failing the batch.

Source

Thrown at src/main/ipc/filesystem-mutations.ts:530

      return { sourcePath, status: 'skipped', reason: 'symlink' }
    }
    return {
      sourcePath,
      status: 'failed',
      reason: error instanceof Error ? error.message : String(error)
    }
  }
}

async function stageDirectoryEntries(rootPath: string): Promise<StagedExternalImportEntry[]> {
  const entries: StagedExternalImportEntry[] = [{ relativePath: '', kind: 'directory' }]
  let totalBytes = 0
  const rootRealPath = await realpath(rootPath)

  async function visit(dirPath: string): Promise<void> {
    const dirStat = await lstat(dirPath)
    if (dirStat.isSymbolicLink()) {
      throw new RuntimeUploadSymlinkError(
        `Symlink not allowed in '${normalizeRelativeUploadPath(relative(rootPath, dirPath))}'`
      )
    }
    if (!dirStat.isDirectory()) {
      throw new Error(
        `Unsupported file type in '${normalizeRelativeUploadPath(relative(rootPath, dirPath))}'`
      )
    }
    await assertRealPathInsideRoot(
      rootRealPath,
      dirPath,
      normalizeRelativeUploadPath(relative(rootPath, dirPath))
    )
    const dirEntries = await readdir(dirPath, { withFileTypes: true })
    for (const entry of dirEntries) {
      const childPath = join(dirPath, entry.name)
      const childRelativePath = normalizeRelativeUploadPath(relative(rootPath, childPath))
      if (entry.isSymbolicLink()) {

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Expect the source to be reported as skipped; copy the real content into a non-symlinked directory and upload that.
  2. Remove or un-symlink the offending directory in the source tree before staging.
  3. Filter symlinked directories out of the upload selection in the UI.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-detect symlinked directories before runtime-upload staging
import { lstat } from 'node:fs/promises'
async function isSymlinkDir(p: string): Promise<boolean> {
  try { return (await lstat(p)).isSymbolicLink() } catch { return false }
}

Try / catch

// stageOneSourceForRuntimeUpload already maps RuntimeUploadSymlinkError -> skipped;
// callers should branch on the result status:
const res = await stageOneSourceForRuntimeUpload(src)
if (res.status === 'skipped' && res.reason === 'symlink') {
  // report to user; offer to upload the real target instead
}

Prevention

When it happens

Trigger: stageOneSourceForRuntimeUpload processes a directory source whose root or a visited subdirectory is a symlink; lstat reports the visited dirPath as isSymbolicLink() and RuntimeUploadSymlinkError is thrown.

Common situations: A dropped/uploaded directory tree that contains symlinked subfolders (common in monorepos, build output dirs, or linked packages); the user selected a top-level path that is itself a symlink to a directory.

Related errors


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