Doctor
polyrepo doctor — a health check in seven sections, mostly read-only diagnosis, plus a few small, non-destructive self-repairs.
polyrepo doctor [options]Worth running first if any other command is behaving unexpectedly, any time you rename a default branch on GitHub, or just periodically to catch accumulated cruft before it piles up.
Example output — a real run, on a folder of repos with nothing seriously wrong, just the two things doctor actually exists to catch:
Environment
✓ Node.js v22.23.2 (>= 20 required).
✓ git version 2.49.0.windows.1.
✓ gh version 2.69.0 (2025-03-19) — authenticated as macrulezru.
! npm 10.9.8 — not authenticated (only needed for `polyrepo publish`). Run `npm login`.
Config
Config file: C:\work\NPM\polyrepo.config.json
Checking 18 package(s)...
✓ 18 package(s) discovered.
Remote sync
Checking 18 package(s) against GitHub, and pruning stale remote-tracking refs...
✓ 18 package(s) checked — local default-branch cache matches GitHub.
✓ os-detect: pruned 2 stale remote-tracking ref(s) (add-more-examples, new-documentation-refactor).
Branch sync
Fetching and comparing 18 package(s) against origin...
✓ 18 package(s) checked — all in sync with origin.
Branch protection
Checking 18 package(s) for branch protection...
✓ 18 package(s) checked — default branch is protected on all of them.
Stale bump branches
Checking 18 package(s) for leftover bump branches with a merged PR...
✓ No stale bump branches found.
Cross-package dependencies
✓ No stale local dependency references found.npm not being logged in is only a warning (publish is the only command that needs it); the two pruned branches on os-detect are refs for PRs merged and deleted on GitHub a while back — doctor is what actually notices and clears them, nothing else in the CLI does.
Sections
Environment — Node.js version (20+ required), whether
git/gh/npmare onPATH, and whethergh/npmare authenticated (npm auth is only a warning — it's only needed forpublish).Config — how many packages the current config actually resolves to, and which repos are dirty, in a detached
HEADstate, or off their default branch.Remote sync — two related repairs, both per-repo pointer refreshes that never touch a file, branch, or commit:
- compares each repo's locally cached default-branch name against what GitHub actually reports right now. Git never refreshes that local cache on its own, so renaming a repo's default branch on GitHub after it was cloned would otherwise go unnoticed by every other command forever — wherever it's drifted, this fixes it with
git remote set-head origin --auto; - runs
git remote prune originon every repo, dropping localremotes/origin/xrefs left over for branches already deleted on GitHub.
Both are skipped for a repo GitHub can't be reached for (offline, or
ghnot authenticated).- compares each repo's locally cached default-branch name against what GitHub actually reports right now. Git never refreshes that local cache on its own, so renaming a repo's default branch on GitHub after it was cloned would otherwise go unnoticed by every other command forever — wherever it's drifted, this fixes it with
Branch sync — fetches and compares each repo's local default branch against
origin/<default>: diverged (both ahead and behind — a fast-forward won't work, needs resolving by hand), behind only (safe to fast-forward withswitch-default), or ahead only (local commits not yet pushed).Branch protection — whether each repo's default branch actually has GitHub branch protection enabled right now. Report-only; enabling protection is a policy decision, not something this fixes on your behalf.
Stale bump branches —
bumpmerges through a PR with the branch intentionally left on origin, so every completed bump leaves a local branch copy behind too, forever. This reports how many local branches match<version>-version-bumpand already have a merged PR. See--clean-branchesbelow.Cross-package dependencies — does any local package's
dependencies/devDependencies/peerDependenciesrange no longer match another local package's current version.
Options
--clean-branches
Turns section 6 into an interactive checkbox — pick which stale local bump branches to delete. Uses git branch -d, which refuses instead of forcing if a branch somehow isn't actually fully merged locally. The branch on origin is never touched — deleting that is out of scope here, it's more sensitive shared state than a local branch nobody else can see.
Example:
polyrepo doctor --clean-branches