{"record":{"id":"fd07a4027482aece","repo":"denoland/deno","slug":"err-fs-cp-einval","errorCode":"ERR_FS_CP_EINVAL","errorMessage":"Invalid src or dest","messagePattern":"Invalid src or dest","errorType":"exception","errorClass":"SystemError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/_fs/cp/cp.ts","lineNumber":60,"sourceCode":"  IsSrcFile: 1n << 34n,\n  IsSrcCharDevice: 1n << 35n,\n  IsSrcBlockDevice: 1n << 36n,\n  IsSrcSymlink: 1n << 37n,\n  IsSrcSocket: 1n << 38n,\n  IsSrcFifo: 1n << 39n,\n};\n\nconst {\n  Number,\n  PromiseResolve,\n  SymbolAsyncIterator,\n} = primordials;\n\nfunction throwCpError(err) {\n  const { EEXIST, EINVAL, EISDIR, ENOTDIR } = lazyConstants();\n  switch (err.kind) {\n    case \"EINVAL\":\n      throw new ERR_FS_CP_EINVAL({\n        message: err.message,\n        path: err.path,\n        syscall: \"cp\",\n        errno: EINVAL,\n        code: \"EINVAL\",\n      });\n    case \"DIR_TO_NON_DIR\":\n      throw new ERR_FS_CP_DIR_TO_NON_DIR({\n        message: err.message,\n        path: err.path,\n        syscall: \"cp\",\n        errno: EISDIR,\n        code: \"EISDIR\",\n      });\n    case \"NON_DIR_TO_DIR\":\n      throw new ERR_FS_CP_NON_DIR_TO_DIR({\n        message: err.message,\n        path: err.path,","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/_fs/cp/cp.ts#L42-L78","documentation":"fs.cp(src, dest) runs a native path check that can reject the operation with CpError::EInval (kind 'EINVAL'), which throwCpError converts into ERR_FS_CP_EINVAL (the class's default message is 'Invalid src or dest'; the actual message comes from the native check). Two conditions produce it in ext/node/ops/fs.rs: src and dest are the same file (identical dev/ino stats — 'src and dest cannot be the same'), and recursive copies where dest lies inside src ('cannot copy X to a subdirectory of self Y').","triggerScenarios":"fs.cp('/a/f.txt', '/a/f.txt') or two hardlinks/symlinks resolving to the same inode; fs.cp('proj', 'proj/copy', { recursive: true }) — copying a directory into itself; a symlinked dest that resolves back to the src directory.","commonSituations":"Build scripts copying a directory into its own subtree (dist inside src); templating code where src and dest strings differ but resolve to the same path; hardlinked cache files used as both source and target.","solutions":["Resolve both paths with path.resolve and assert they differ before calling fs.cp","Make dest a sibling, not a child: fs.cp('proj', '../proj-copy', { recursive: true })","Use fs.realpath on both sides to catch symlink/hardlink identity, not just string equality"],"exampleFix":"// before\nfs.cp('proj', 'proj/dist/backup', { recursive: true }, cb); // dest inside src\n\n// after\nfs.cp('proj', 'backup', { recursive: true }, cb); // sibling destination","handlingStrategy":"try-catch","validationCode":"const a = fs.realpathSync(src), b = fs.realpathSync(dest);\nif (a === b || b.startsWith(a + path.sep)) throw new Error('cp: dest must not equal or live inside src');","typeGuard":null,"tryCatchPattern":"try { await fs.promises.cp(src, dest, opts); } catch (e) { if (e.code === 'ERR_FS_CP_EINVAL') { /* fix paths, never retry unchanged */ } else throw e; }","preventionTips":["Always resolve both paths before copying","Keep backup destinations outside the source tree","Beware hardlinks: string equality is not identity — compare realpath"],"tags":["node-compat","fs","cp","copy","einval","err-fs-cp-einval","deno"],"backgroundTag":"fs-copy-invalid-path","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}