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 the host, or just periodically to catch accumulated cruft before it piles up.
Repos are discovered before anything is printed, so the Environment section below already knows which host(s) they actually use — see "Multiple hosts" further down.
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 their host, and pruning stale remote-tracking refs...
✓ 18 package(s) checked — local default-branch cache matches the host.
✓ 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 the host a while back — doctor is what actually notices and clears them, nothing else in the CLI does. This particular folder is all-GitHub, which is why only gh shows up in Environment — see below.
Multiple hosts
Every discovered repo's host (GitHub or GitLab) is detected up front, from its origin remote — a folder can freely mix both. The Environment section only checks gh if at least one repo is on GitHub, and only checks glab if at least one is on GitLab — a GitHub-only setup is never asked to install a tool it has no use for, and vice versa. Remote sync and Branch protection route each repo to the right host automatically; a repo whose host can't be determined (no origin remote) is reported the same way an unreachable host is — "could not be checked" — rather than causing an error.
Sections
Environment — Node.js version (20+ required), whether
git/npmare onPATHandnpmauthenticated (a warning only — it's only needed forpublish), plusghand/orglab— whichever the discovered repos actually use — onPATHand authenticated.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 its host actually reports right now. Git never refreshes that local cache on its own, so renaming a repo's default branch on the host 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 the host.
Both are skipped for a repo whose host can't be reached (offline,
gh/glabnot authenticated, or no recognized host).- compares each repo's locally cached default-branch name against what its host actually reports right now. Git never refreshes that local cache on its own, so renaming a repo's default branch on the host 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 branch protection enabled right now, on its own host. Report-only; enabling protection is a policy decision, not something this fixes on your behalf.
Stale bump branches —
bumpmerges through a PR/MR 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/MR. 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