credoctor  ·  v0.1.1  ·  MIT

Prove your git credentials are actually correct.

Not "read the config files and hope". credoctor makes the real calls — SSH greetings, remote fetches, per-token org access — and tells you which of your identities is lying to you.

npx credoctor doctor

Nothing to clone. Node 20+. Tested on Linux, macOS and Windows in CI.


The problem

Every file was correct. The system was still wrong.

These four failures happened while setting up five separated trees on one Mac. Every config file involved was right. Nothing a config-inspecting tool reads had changed. Each one was only visible by asking the other side.

The key that was registered but not authorized

An SSH key sat on the right account, but the org enforces SAML SSO and the key was never authorized for it.

~/.ssh/config is perfect. Only a real fetch fails.

The login that broke a different project

One gh auth login wrote an unscoped github.com credential into the keychain and silently broke another tree's remotes.

No config file changed at all.

The override that beat includeIf

A repo's local user.email quietly overrode the tree's includeIf, so commits went out under a stale identity.

Both files were correct. The winner was the third one.

The token and key that disagreed

An OAuth token was SSO-authorized while the matching SSH key was not. They authorize separately, and nothing local records either.

Two independent remote states, both invisible.

The unit that matters isn't "a git identity" — it's a credential domain: everything that must agree for one directory tree, plus the remote-side state no local file records. Verify reality, not configuration.

What it looks like

One row per tree, per check.

$ npx credoctor doctor

credoctor doctor — 7 tree(s), config ./credoctor.json

  client
    pass  identity             you-at-client <you@client.example>
    pass  no-local-override    no repo overrides the tree identity
    pass  signing-key-email    key 9F0E1D2C… is bound to you@client.example
    pass  remote-alias         6 repo(s) all use git@github-client
    pass  ssh-identity         greeted as you-at-client
    pass  remote-reachable     ~/code/client/platform reachable
    pass  gh-account           you-at-client (~/.config/gh-client)
    pass  gh-org-access        reaches 6 repo(s), e.g. clientco/platform
    pass  gh-org-isolation     denied all 10 repo(s) belonging to other trees

That last line is the point. It didn't read a config file — it asked GitHub, with that token, whether it could reach another tree's repositories, and confirmed it couldn't.

The catalogue

Thirteen checks, each named for the failure it exists to catch.

CheckKindCatches
Tree identity resolveslocalA missing or mis-scoped includeIf
No local user.email overridelocalThe override that silently wins over includeIf
No identity outside a treelocalCommits stamped you@hostname in a stray clone
Signing key bound to tree emaillocal"Unverified" commits; a key reused across orgs
Signing key not expiredlocalSignatures that quietly stop verifying
Remotes use the tree's SSH aliaslocalA remote falling back to the shared keychain credential
SSH greeting per aliasnetworkWrong key, unregistered key
Real reachabilitynetworkSSO-unauthorized key — the failure config cannot show
gh resolves per storenetworkStore / keyring mismatch
Per-token org access and denialnetworkToken drift; over-broad access
No two trees share an alias, store or keylocalThe coupling that makes one login break another tree
No unscoped github.com credentiallocalThe keychain entry one gh auth login overwrites
Browser profile maps to the treelocalLinks opening as the wrong identity

★ The differentiated three. They are the failures that cost real time, and no existing tool tests them. Full catalogue and reasoning in DESIGN.md.

Use

Two commands.

npx credoctor import --write credoctor.jsonDraft a config from the includeIf blocks already in your ~/.gitconfig.
npx credoctor doctorCheck every tree. Exits non-zero on any failure, so it fits a shell hook or CI.
npx credoctor doctor --offlineLocal checks only, no network call.
npx credoctor doctor --jsonMachine-readable output.

import infers each tree's SSH alias and orgs from the remotes actually in use. You shouldn't have to hand-write a config describing a machine you already set up. A failure prints the remedy, not just the fact — for an SSO-unauthorized key, the settings URL and the exact button, because that is the step people miss.

Status

v0.1, read-only — and honest about the gaps.

doctor and import only. Provisioning (credoctor add, credoctor apply) writes to ~/.gitconfig, ~/.ssh/config and the keychain. That blast radius isn't worth taking until the checks have earned trust.

Non-GitHub hosts. Bitbucket and GitLab remotes are detected, but their auth isn't understood — they surface as a generic reachability failure.

Browsers. The check covers the Chromium family (Chrome, Chromium, Edge, Brave), which share one Local State format. Firefox and Safari aren't read yet.

Trust

A tool that inspects credentials should show its own papers.

credoctor never reads, prints or moves a secret — it asks subsystems whether they work. It also invokes binaries directly, never through a shell, because a gh() wrapper that forces GH_CONFIG_DIR from $PWD will otherwise override the environment credoctor sets and report the shell's opinion instead of the store you asked about.

Every release from v0.1.1 onward is published from CI by npm trusted publishing — no token exists in the repository — and carries a provenance attestation tying the tarball to a commit, a tag and a workflow run. You do not have to take that on faith:

npm audit signaturesVerifies the registry signature and the provenance attestation of what you installed.

v0.1.0 was hand-published before that pipeline existed and carries no attestation. Releases from v0.1.1 on do. The mechanics, and the trap that cost the first attempt, are written up in RELEASING.md.