{"record":{"id":"48447994b7203973","repo":"stablyai/orca","slug":"file-changed-during-import-basename-srcpath","errorCode":null,"errorMessage":"File changed during import: '${basename(srcPath)}'","messagePattern":"File changed during import: '(.+?)'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/filesystem-mutations.ts","lineNumber":738,"sourceCode":"\n  let destinationCreated = false\n  const sourceHandle = await open(srcPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))\n  let destinationHandle: Awaited<ReturnType<typeof open>> | null = null\n  try {\n    const openedStat = await sourceHandle.stat()\n    if (\n      !openedStat.isFile() ||\n      (typeof beforeOpenStat.size === 'number' && openedStat.size !== beforeOpenStat.size) ||\n      (typeof beforeOpenStat.ino === 'number' &&\n        beforeOpenStat.ino !== 0 &&\n        openedStat.ino !== 0 &&\n        openedStat.ino !== beforeOpenStat.ino) ||\n      (typeof beforeOpenStat.dev === 'number' &&\n        beforeOpenStat.dev !== 0 &&\n        openedStat.dev !== 0 &&\n        openedStat.dev !== beforeOpenStat.dev)\n    ) {\n      throw new Error(`File changed during import: '${basename(srcPath)}'`)\n    }\n    // Why: copyFile(path, path) would follow a source symlink if the source is\n    // swapped after validation. Streaming from an O_NOFOLLOW handle keeps the\n    // authorized file identity pinned for the copy.\n    destinationHandle = await open(dstPath, 'wx')\n    destinationCreated = true\n    await pipeline(sourceHandle.createReadStream(), destinationHandle.createWriteStream())\n  } catch (error) {\n    if (destinationCreated) {\n      await unlink(dstPath).catch(() => {})\n    }\n    throw error\n  } finally {\n    await sourceHandle.close().catch(() => {})\n    await destinationHandle?.close().catch(() => {})\n  }\n}\n","sourceCodeStart":720,"sourceCodeEnd":756,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/filesystem-mutations.ts#L720-L756","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the source file is not being modified during the import.","Copy the file to a stable local path before importing.","Retry the import after the source has stabilized."],"exampleFix":"// before: import a file that a build tool is atomically replacing\n// after: snapshot the file first\n//   cp /project/output.bin /tmp/stable.bin\n//   import /tmp/stable.bin","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await importLocalSource(sourcePath, destDir)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('File changed during import')) {\n    showUserWarning('The source file changed during import. Ensure it is stable and retry.')\n    return\n  }\n  throw error\n}","preventionTips":["Do not import files that are being rewritten by editors, builds, or atomic-rename daemons.","Snapshot volatile files with `cp` before importing.","Retry after the source has stabilized if the race is transient."],"tags":["toctou","filesystem","local-import","race-condition","security","ipc"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}