{"record":{"id":"b95d6eb3241705e2","repo":"pnpm/pnpm","slug":"git-unclean","errorCode":"GIT_UNCLEAN","errorMessage":"Unclean working tree. Commit or stash changes first.","messagePattern":"Unclean working tree\\. Commit or stash changes first\\.","errorType":"exception","errorClass":"PnpmError","httpStatus":null,"severity":"error","filePath":"pnpm11/releasing/commands/src/publish/publish.ts","lineNumber":185,"sourceCode":"  opts: Omit<PublishRecursiveOpts, 'workspaceDir'> & {\n    argv: {\n      original: string[]\n    }\n    engineStrict?: boolean\n    recursive?: boolean\n    workspaceDir?: string\n  } & Pick<Config, 'bin' | 'gitChecks' | 'ignoreScripts' | 'pnpmHomeDir' | 'publishBranch' | 'embedReadme' | 'packGzipLevel' | 'skipManifestObfuscation' | 'versioning'>\n  & Pick<ConfigContext, 'allProjects'>,\n  params: string[]\n): Promise<PublishResult> {\n  if (opts.batch && !opts.recursive) {\n    throw new PnpmError('BATCH_PUBLISH_REQUIRES_RECURSIVE', '--batch can only be used together with --recursive', {\n      hint: 'Run \"pnpm publish -r --batch\" to publish all workspace packages in a single request.',\n    })\n  }\n  if (opts.gitChecks !== false && await isGitRepo()) {\n    if (!(await isWorkingTreeClean())) {\n      throw new PnpmError('GIT_UNCLEAN', 'Unclean working tree. Commit or stash changes first.', {\n        hint: GIT_CHECKS_HINT,\n      })\n    }\n    const branches = opts.publishBranch ? [opts.publishBranch] : ['master', 'main']\n    const currentBranch = await getCurrentBranch()\n    if (currentBranch === null) {\n      throw new PnpmError(\n        'GIT_UNKNOWN_BRANCH',\n        `The Git HEAD may not attached to any branch, but your \"publish-branch\" is set to \"${branches.join('|')}\".`,\n        {\n          hint: GIT_CHECKS_HINT,\n        }\n      )\n    }\n    if (!branches.includes(currentBranch)) {\n      let isConfirmed: boolean\n      try {\n        isConfirmed = await confirm({","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/pnpm/pnpm/blob/6261b7f388016d57ca6b90340342411cd1d0d00f/pnpm11/releasing/commands/src/publish/publish.ts#L167-L203","documentation":"pnpm publish runs git safety checks by default (disable with --no-git-checks); the first one requires a clean working tree, verified via isWorkingTreeClean(). Any uncommitted modification or untracked file at publish time triggers this error so that the published artifact always corresponds to a reviewable git state.","triggerScenarios":"Running pnpm publish when 'git status --porcelain' is non-empty: edited files not committed, untracked build output, or a version bump written by 'pnpm version' that was never committed.","commonSituations":"Forgetting to commit after 'pnpm version'/'pnpm change version' bumps package.json; changeset files left untracked; generated files (dist/, coverage/) not gitignored; publishing from a machine with unrelated local edits.","solutions":["Inspect 'git status --porcelain' to see exactly what is dirty, then commit it (git add + git commit) or stash it (git stash)","Add generated output to .gitignore so it stops dirtying the tree","If the dirty state is expected or checks are handled elsewhere in CI, bypass with 'pnpm publish --no-git-checks'"],"exampleFix":"# before\npnpm version patch && pnpm publish   # version bump left uncommitted\n\n# after\npnpm version patch && git add -A && git commit -m 'chore: version' && pnpm publish","handlingStrategy":"validation","validationCode":"// Fail fast in release scripts before calling pnpm publish\nimport { execSync } from 'node:child_process'\n\nconst dirty = execSync('git status --porcelain', { encoding: 'utf8' }).trim()\nif (dirty !== '') {\n  fail(`Refusing to publish: uncommitted changes:\\n${dirty}`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await publish(opts)\n} catch (err) {\n  if (err instanceof PnpmError && err.code === 'GIT_UNCLEAN') {\n    // surface the hint, offer to stash: execSync('git stash') then retry once\n  }\n}","preventionTips":["Commit immediately after pnpm version / changeset version bumps","Gitignore generated output (dist, coverage) so builds never dirty the tree","Keep publish as the last step of a script that ends with git push"],"tags":["publish","git","validation"],"backgroundTag":"dirty-working-tree","analyzedSha":"6261b7f388016d57ca6b90340342411cd1d0d00f","analyzedAt":"2026-08-17T18:30:54.750Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}