stablyai/orca · error · Error

File changed during import

Error message

File changed during import: '${basename(srcPath)}'

What it means

Thrown in copyLocalFileNoFollow after opening the source with O_NOFOLLOW. The post-open fstat (openedStat) is compared against the pre-open lstat (beforeOpenStat): size, inode, and device must all match. If any differ, the file was swapped between the lstat and the open. Streaming from the O_NOFOLLOW handle pins the authorized file identity, but this check ensures the handle actually refers to the file that was validated.

Solutions

  1. Ensure the source file is not being modified during the import.
  2. Copy the file to a stable local path before importing.
  3. Retry the import after the source has stabilized.

Example fix

// before: import a file that a build tool is atomically replacing
// after: snapshot the file first
//   cp /project/output.bin /tmp/stable.bin
//   import /tmp/stable.bin
Defensive patterns

Strategy: retry

Try / catch

try {
  await importLocalSource(sourcePath, destDir)
} catch (error) {
  if (error instanceof Error && error.message.includes('File changed during import')) {
    showUserWarning('The source file changed during import. Ensure it is stable and retry.')
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Between the beforeOpenStat lstat and the O_NOFOLLOW open, the source file is replaced, truncated, or moved such that its size, inode, or device number changes. The open succeeds but fstat reports a different identity.

Common situations: A file being rewritten by an editor or build tool during import. Atomic file replacement (write-to-temp-then-rename) happening between lstat and open. Importing from a network filesystem where inode/device mapping is not stable.

Related errors


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

Appendix: source

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

  let destinationCreated = false
  const sourceHandle = await open(srcPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))
  let destinationHandle: Awaited<ReturnType<typeof open>> | null = null
  try {
    const openedStat = await sourceHandle.stat()
    if (
      !openedStat.isFile() ||
      (typeof beforeOpenStat.size === 'number' && openedStat.size !== beforeOpenStat.size) ||
      (typeof beforeOpenStat.ino === 'number' &&
        beforeOpenStat.ino !== 0 &&
        openedStat.ino !== 0 &&
        openedStat.ino !== beforeOpenStat.ino) ||
      (typeof beforeOpenStat.dev === 'number' &&
        beforeOpenStat.dev !== 0 &&
        openedStat.dev !== 0 &&
        openedStat.dev !== beforeOpenStat.dev)
    ) {
      throw new Error(`File changed during import: '${basename(srcPath)}'`)
    }
    // Why: copyFile(path, path) would follow a source symlink if the source is
    // swapped after validation. Streaming from an O_NOFOLLOW handle keeps the
    // authorized file identity pinned for the copy.
    destinationHandle = await open(dstPath, 'wx')
    destinationCreated = true
    await pipeline(sourceHandle.createReadStream(), destinationHandle.createWriteStream())
  } catch (error) {
    if (destinationCreated) {
      await unlink(dstPath).catch(() => {})
    }
    throw error
  } finally {
    await sourceHandle.close().catch(() => {})
    await destinationHandle?.close().catch(() => {})
  }
}

View on GitHub (pinned to 1136503c6a)