stablyai/orca · error · Error

File changed during upload staging: '${displayPath}'

Error message

File changed during upload staging: '${displayPath}'

What it means

Thrown in stageFileEntry after O_NOFOLLOW open. The code compares the post-open fstat (size, inode, device) against the pre-scan lstat result. If any field differs, the file was swapped or truncated between the lstat and the open. This pins the authorized file identity so that a race cannot substitute a different file body for the one that was validated.

Source

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

    await assertRealPathInsideRoot(options.rootRealPath, filePath, displayPath)
  }
  const initialTotalBytes =
    options?.totalBytesBefore === undefined
      ? statResult.size
      : options.totalBytesBefore + statResult.size
  assertRemoteUploadBudget(relativePath, statResult.size, initialTotalBytes)
  const fileHandle = await open(filePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))
  try {
    const openedStat = await fileHandle.stat()
    if (!openedStat.isFile()) {
      throw new Error(`Unsupported file type in '${displayPath}'`)
    }
    if (
      openedStat.size !== statResult.size ||
      (statResult.ino !== 0 && openedStat.ino !== 0 && openedStat.ino !== statResult.ino) ||
      (statResult.dev !== 0 && openedStat.dev !== 0 && openedStat.dev !== statResult.dev)
    ) {
      throw new Error(`File changed during upload staging: '${displayPath}'`)
    }
    const totalBytes =
      options?.totalBytesBefore === undefined
        ? openedStat.size
        : options.totalBytesBefore + openedStat.size
    assertRemoteUploadBudget(relativePath, openedStat.size, totalBytes)
    const buffer = await fileHandle.readFile()
    const afterReadStat = await fileHandle.stat()
    if (afterReadStat.size !== openedStat.size) {
      throw new Error(`File changed during upload staging: '${displayPath}'`)
    }
    return {
      entry: {
        relativePath: displayPath,
        kind: 'file',
        contentBase64: buffer.toString('base64')
      },
      byteLength: openedStat.size

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Re-run the staging when the source tree is quiescent — no concurrent writes.
  2. If the file is a log or output being actively written, snapshot it (copy to a stable temp location) before importing.
  3. On network filesystems, ensure the mount is stable and not remounting during the import.

Example fix

// before: stage a file that is being actively appended to
// after: snapshot first, then stage the snapshot
//   cp /var/log/app.log /tmp/stable/app.log
//   import /tmp/stable/app.log
Defensive patterns

Strategy: retry

Try / catch

try {
  await stageRemoteImport(sourcePath)
} catch (error) {
  if (error instanceof Error && error.message.includes('File changed during upload staging')) {
    // file was being written concurrently; retry after a brief pause
    return stageRemoteImport(sourcePath) // one retry
  }
  throw error
}

Prevention

When it happens

Trigger: Between the initial lstat (line 577) and the O_NOFOLLOW open (line 593), a concurrent process replaces, truncates, or moves the file such that size, inode number, or device number changes. The open succeeds but fstat reports a different identity than the lstat.

Common situations: A log file being rotated during staging. A file being rewritten by an editor save or build step. NFS or network filesystems where inode/device mapping is unstable. Importing from a watched directory where file-watchers trigger modifications.

Related errors


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