{"record":{"id":"cb3493e52c7f5c8c","repo":"pnpm/pnpm","slug":"invalid-patch-dir","errorCode":"INVALID_PATCH_DIR","errorMessage":"${userDir} is not a valid patch directory","messagePattern":"(.+?) is not a valid patch directory","errorType":"exception","errorClass":"PnpmError","httpStatus":null,"severity":"error","filePath":"pnpm11/patching/commands/src/patchCommit.ts","lineNumber":69,"sourceCode":"    usages: ['pnpm patch-commit <patchDir>'],\n  })\n}\n\ntype PatchCommitCommandOptions = install.InstallCommandOptions & Pick<Config, 'patchesDir' | 'patchedDependencies'> & Pick<ConfigContext, 'rootProjectManifest' | 'rootProjectManifestDir'>\n\nexport async function handler (opts: PatchCommitCommandOptions, params: string[]): Promise<string | undefined> {\n  const userDir = params[0]\n  const lockfileDir = (opts.lockfileDir ?? opts.dir ?? process.cwd()) as ProjectRootDir\n  const patchesDirName = normalizePath(path.normalize(opts.patchesDir ?? 'patches'))\n  const patchesDir = path.join(lockfileDir, patchesDirName)\n  const patchedPkgManifest = await readPackageJsonFromDir(userDir)\n  const editDir = path.resolve(opts.dir, userDir)\n  const stateValue = readEditDirState({\n    editDir,\n    modulesDir: path.join(lockfileDir, opts.modulesDir ?? 'node_modules'),\n  })\n  if (!stateValue) {\n    throw new PnpmError('INVALID_PATCH_DIR', `${userDir} is not a valid patch directory`, {\n      hint: 'A valid patch directory should be created by `pnpm patch`',\n    })\n  }\n  const { applyToAll } = stateValue\n  const nameAndVersion = `${patchedPkgManifest.name}@${patchedPkgManifest.version}`\n  const patchKey = applyToAll ? patchedPkgManifest.name : nameAndVersion\n  let gitTarballUrl: string | undefined\n  if (!applyToAll) {\n    gitTarballUrl = await getGitTarballUrlFromLockfile({\n      alias: patchedPkgManifest.name,\n      bareSpecifier: patchedPkgManifest.version || undefined,\n    }, {\n      lockfileDir,\n      modulesDir: opts.modulesDir,\n      virtualStoreDir: opts.virtualStoreDir,\n    })\n  }\n  const patchedPkg = parseWantedDependency(gitTarballUrl ? `${patchedPkgManifest.name}@${gitTarballUrl}` : nameAndVersion)","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/pnpm/pnpm/blob/6261b7f388016d57ca6b90340342411cd1d0d00f/pnpm11/patching/commands/src/patchCommit.ts#L51-L87","documentation":"`pnpm patch-commit <dir>` only accepts an edit directory created by `pnpm patch`: it reads `<modulesDir>/.pnpm_patches/state.json`, keyed by the absolute edit-dir path, to recover which package the dir belongs to (name, applyToAll). If there is no state entry for the resolved directory, the dir is rejected as not a valid patch directory.","triggerScenarios":"Passing an arbitrary directory you edited manually; passing a relative path from a different cwd so path.resolve(opts.dir, userDir) produces a different absolute key than stored; moving or renaming the edit dir after `pnpm patch`; deleting node_modules between patch and patch-commit (state.json lives inside it).","commonSituations":"Hand-created edit dirs; `rm -rf node_modules` or a fresh CI stage between the patch and commit steps; running patch-commit from a different workspace directory than patch.","solutions":["Re-create the dir properly: `pnpm patch <pkg>`, redo your edits there, then `pnpm patch-commit <printed-path>`","Pass the exact path pnpm printed and run patch-commit from the same working directory","Never delete node_modules or move the edit dir between patch and patch-commit"],"exampleFix":"# before\npnpm patch-commit ./my-manual-edit-dir   # ERR_PNPM_INVALID_PATCH_DIR\n\n# after\npnpm patch lodash\n# ...edit files in node_modules/.pnpm_patches/lodash@4.17.21...\npnpm patch-commit node_modules/.pnpm_patches/lodash@4.17.21","handlingStrategy":"validation","validationCode":"import fs from 'node:fs'\nimport path from 'node:path'\n\nconst editDir = path.resolve(opts.dir, userDir)\nconst statePath = path.join(lockfileDir, opts.modulesDir ?? 'node_modules', '.pnpm_patches', 'state.json')\nconst state = fs.existsSync(statePath) ? JSON.parse(fs.readFileSync(statePath, 'utf8')) : {}\nif (!(editDir in state)) {\n  throw new Error(`${userDir} was not created by pnpm patch; run it first`)\n}","typeGuard":"function isPnpmEditDir (dir: string, stateFile: string): boolean {\n  if (!fs.existsSync(stateFile)) return false\n  const state = JSON.parse(fs.readFileSync(stateFile, 'utf8')) as Record<string, unknown>\n  return dir in state\n}","tryCatchPattern":null,"preventionTips":["Always create the edit dir via `pnpm patch` and pass the exact printed path back to patch-commit","Run patch and patch-commit from the same working directory","Do not delete node_modules between patch and patch-commit — the state file lives in node_modules/.pnpm_patches/state.json"],"tags":["pnpm","patching","state","cli"],"backgroundTag":"missing-state-file","analyzedSha":"6261b7f388016d57ca6b90340342411cd1d0d00f","analyzedAt":"2026-08-17T18:30:54.750Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}