ref:main

Clone with the login token, and add SSH keys automatically #59

merged colechristensen cole.christensen@gmail.com wants to merge worktree-clone-auth-ssh-key-auto into main

anvil auth login did not get you a working checkout. Two gaps:

repo clone only ever spoke SSH. It built git@host:org/repo.git unconditionally, so a freshly logged-in user was rejected by git with “Permission denied (publickey)” — a message naming neither Anvil nor the fix. The server accepts the login token for git-over-HTTPS (Basic auth, password = PAT, username ignored), so that credential existed the whole time and was never offered.

Clone now defaults to HTTPS with the login token. --ssh opts back in.

The token is not written into the clone:

  • not in the remote URL (that persists a secret into .git/config and survives auth rotate),
  • not in -c http.extraHeader (that exposes it in argv to every process on the machine).

Instead git is pointed at the anvil binary as a credential helper, scoped to the configured server so the token is never offered to another host. The helper is written into the new clone too, so git pull/git push keep working without a prompt. New hidden subcommand: anvil auth git-credential.

ssh-key add required --name and --key-file. Now bare anvil ssh-key add (or --auto):

  • finds the best key in ~/.ssh — Ed25519 > ECDSA > RSA > DSA, stable ordering, skipping certificates and known_hosts;
  • names it from the key’s own comment, else user@host;
  • skips the upload when the fingerprint is already registered, so it is safe to re-run from a setup script;
  • recovers a deleted public half via ssh-keygen -y instead of generating a second key beside the good one;
  • with no key and a terminal, walks through creating one (type, path, comment; ssh-keygen owns the passphrase prompt so it never touches our argv or memory);
  • with no terminal, refuses unless --yes — minting a credential silently inside a script is not a default.

Public keys are parsed and fingerprinted in-process (SHA256, OpenSSH display form). That parser is also what refuses to upload a private key passed by mistake.

Verification

Against the live server:

  • fresh clone of fangorn/faraday authenticates with the login token alone, no SSH key involved;
  • .git/config holds a plain remote URL and the scoped helper — no token;
  • subsequent git fetch succeeds with GIT_TERMINAL_PROMPT=0, proving the helper answers the 401 unattended;
  • the helper emits zero bytes for a github.com challenge;
  • live discovery on a machine holding both key types picks ~/.ssh/id_ed25519.pub.

699 tests pass (up from 684); clippy clean under -D warnings; cargo fmt --check clean. New: 10 end-to-end tests for the auto path against a mock server with a redirected ~/.ssh, plus unit tests for key parsing/fingerprinting/discovery and the credential-helper protocol.

Backwards compatible: the old ssh-key add --name X --key-file Y form still works, and the clone JSON still carries ssh_url.

🤖 Generated with Claude Code

Created Aug 05, 2026 at 21:57 UTC | Merged Aug 06, 2026 at 03:55 UTC by colechristensen cole.christensen@gmail.com