Two transports, one rule: pick by visibility, not by owner.Every repo across the org has exactly one canonical transport. The visibility of the repo decides which.
The rule
The owner — personal account, org, fork — does not matter. A public dryvist repo gets SSH; a private personal repo gets HTTPS. The transport is a property of the repo, not the account.
Why split this way
The two scenarios that drive the rule:Public repos use SSH because there’s nothing to gate
A public repo’s read path needs no auth (anyone cangit clone https://…), and the write path is gated by GitHub-side branch protection and ruleset rules rather than client-side credentials. Routing public pushes through SSH:
- Skips per-command credential lookups entirely — no token to mint, no lease to take.
- Keeps GitHub token scope completely orthogonal to public writes. Even a read-only token can push to a public repo over SSH, because the SSH key, not the token, is what GitHub authenticates.
- Works fine on hosts where no token is configured at all (CI runners with deploy keys, for example).
Private repos use HTTPS because the credential lookup gates writes
A private repo’s write path is only gated by client-side credentials. If a private repo used SSH:- An unlocked SSH key is always on. It would auto-authenticate to every private repo the user can reach — no per-repo scope, no record of which tool initiated the push.
- Any process with read access to
~/.ssh/id_*could push, because SSH is binary “key works” or “key doesn’t” — there is no fine-grained scope on the SSH side.
git push. A credential helper answers that lookup, and it answers with a token scoped to the repository being pushed — not a standing credential for everything the user can reach. So each push is authorized on its own, one repository at a time, and the least-privilege gate stays meaningful for writes.
Without an appropriately-scoped credential, a private push fails at the lookup. That failure is the point: it makes the user (or AI agent) escalate explicitly — and visibly, for that one repository — before any write reaches the remote. See GitHub access for what the helper mints and how the tiers are gated.
Setting the right remote URL
After cloning, verify and fix the remote if wrong:What this does not cover
- Per-machine credential wiring — which helper is configured, and how it is bootstrapped. That is host setup; it lives in private user-level documentation, not on this public site. Public docs explain that a credential lookup happens and what it is scoped to; the exact local wiring is operational detail.
- CI authentication. GitHub Actions, deploy keys, and OIDC federation use their own per-job credentials wired into the workflow. The SSH-vs-HTTPS rule is for local developer transports, not CI.
- Submodules. Treat the submodule URL the same way: public submodule → SSH, private submodule → HTTPS, regardless of how the parent repo is cloned.
What this connects to
Branch conventions
Branch names are transport-agnostic. The conventions are the same on either remote.
PR conventions
PR creation uses the
gh CLI. It needs an equivalent scoped token, but reaches it by its own route — see GitHub access.Golden laws
The non-negotiable rules around secrets, force-pushes, and admin operations.
GitHub authentication docs
Upstream reference on connecting to GitHub over SSH and HTTPS.