Skip to main content
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

Repo visibilityTransportRemote URL shapeAuthenticates via
PublicSSHgit@github.com:<owner>/<repo>.gitUnlocked SSH key
PrivateHTTPShttps://github.com/<owner>/<repo>.gitGitHub token (PAT or fine-grained)
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 can git 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 (no token timeout, no keychain unlock).
  • Keeps the GitHub PAT scope completely orthogonal to public writes. Even a read-only PAT can push to a public repo over SSH, because the SSH key, not the PAT, is what GitHub authenticates.
  • Works fine on hosts where no PAT is configured at all (CI runners with deploy keys, for example).

Private repos use HTTPS because the token tier needs to gate writes

A private repo’s write path is only gated by client-side credentials. If a private repo used SSH:
  • An unlocked SSH key would auto-authenticate to every private repo the user can reach — no per-repo scope, no per-tier scope, no audit trail 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.
Routing private pushes through HTTPS forces the git client to look up a credential on every git push. That credential lookup goes through the platform’s token store (macOS keychain, git-credential-manager, OS keyring, etc.), which can hold tokens scoped narrowly — read-only on most repos, read+write on a specific set, admin on a smaller set. The same tier system that gates gh API calls now also gates git push. Without an appropriately-scoped token in the credential store, a private push fails at the credential prompt. That failure is the point: it makes the user (or AI agent) escalate explicitly to a higher-privilege token before any write reaches the remote.

Setting the right remote URL

After cloning, verify and fix the remote if wrong:
# Inspect
git remote -v

# Fix a private repo that was accidentally cloned over SSH
git remote set-url origin https://github.com/<owner>/<repo>.git

# Fix a public repo that was accidentally cloned over HTTPS
git remote set-url origin git@github.com:<owner>/<repo>.git
When in doubt about visibility:
gh repo view <owner>/<repo> --json visibility --jq .visibility
# PUBLIC  → use SSH
# PRIVATE → use HTTPS

What this does not cover

  • Specific token tiers, PAT scopes, or which keychain holds which token. That’s per-machine setup; it lives in private user-level documentation, not on this public site. Public docs explain that a credential lookup happens; the which credential and how is private 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, which respects the same token tier as a private git push.

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.