bug(clone): full-URL clone of forge X writes the ambient forge Y's credential wiring into the new repo - born broken, and auth login cannot repair it #197

Closed
opened 2026-07-17 14:21:54 +00:00 by hexajon · 0 comments
hexajon commented 2026-07-17 14:21:54 +00:00 (Migrated from codeberg.org)

Repro

Machine state: ambient madtea credentials configured for forge Y (global git config: claude @ gitea.example). Forge X (codeberg.org) hosts the repo being cloned; the machine also has a host-keyed systemd-creds backend.

cd /some/scratch
madtea clone https://codeberg.org/sixfold_space/madtea.git
#   -> "Cloned https://codeberg.org/sixfold_space/madtea.git from gitea.example ..."  (misleading, see below)
cd madtea
madtea issue list
#   -> Error: API error 404: not found
madtea auth login        # reconfigure; flow says "Authenticating to codeberg.org", verifies, "Setup complete! hexajon @ codeberg.org"
madtea issue list
#   -> Error: API error 404: not found   (unchanged)
madtea auth whoami
#   -> URL: https://gitea.example / User: claude / Source: repo config / Repository: sixfold_space/madtea  (unchanged)

The fresh clone is unusable for every repo-scoped API command, and the documented remedy (auth login) completes successfully while fixing nothing. The only escape is hand-editing the repo's git config, which no user should need.

Root cause

internal/service/git/clone.go resolves credentials via ambient GetCredentials (clone.go:120) without ever comparing the resolved forge against the host the user explicitly named in the URL. Three consequences, worst first:

  1. Wrong-forge credential wiring written into the new repo (clone.go:199-203): WriteRepoCredentials stamps madtea.url = <forge Y> + madtea.user = <Y's user> + the backend marker into a repo whose origin is forge X. A local madtea.url is the FIRST link in the ADR 0025 resolution chain (config.go:305, ahead of the origin remote), so this override poisons every subsequent command: they target Y with the owner/repo slug parsed from the X origin (here sixfold_space/madtea, which on Y is named differently) -> 404.
  2. auth login cannot repair it: RunFirstTimeSetup correctly derives the forge from origin (setup.go:227-230, ADR 0025 par. 2), authenticates to X, and stores the token host-keyed - but never notices or clears the local madtea.url override pointing at Y. The login reports success and changes nothing observable; whoami before and after are byte-identical.
  3. Misleading output: CloneResult.ForgeHost is taken from the ambient forge whenever credentials resolve (clone.go:124-126) - the actual-URL-host branch only runs with no credentials configured at all. So the user is told the clone came "from gitea.example" when git actually fetched from codeberg.org (the clone URL passes through untouched, clone.go:115; the askpass is host-scoped to Y per #1826, so X received no credentials and the clone only worked because the repo is public).

Removing the wrong-host override is also all it takes for the existing machinery to work: in a bare dir with only origin -> codeberg.org and no local madtea.url, the ADR 0025 origin-first chain + host-keyed backend resolve hexajon @ codeberg.org perfectly (verified live while diagnosing this).

Expected

  • A full-URL clone treats the URL's host as the forge, full stop. Resolve credentials FOR THAT HOST (host-keyed backend / matching config); when none exist, take the #163 unauthenticated public path with its auth note. Never write another forge's madtea.url/madtea.user into the new clone - either write wiring that matches the origin host or write no URL override at all and let the origin-first chain do its job.
  • Clone output names the host git actually fetched from.
  • Defense in depth: origin-derived auth login should detect a local madtea.url override naming a DIFFERENT host than origin, and repair it (or at least warn loudly) instead of silently succeeding into an unchanged broken state. Split into a follow-up if preferred.
  • #97 fixed the shorthand-clone half of this trap (say which forge, warn on mirror=true) - the full-URL path got the display but kept the ambient wiring.
  • #163 added the unauthenticated public-clone path this case should fall into when the URL's host has no credentials.
  • #29 is the same one-default-forge assumption on the MCP surface.

Observed on v0.15.4-0.20260717031522-615d2732ccad; line numbers from main @ 86e55df6.

## Repro Machine state: ambient madtea credentials configured for forge Y (global git config: `claude @ gitea.example`). Forge X (`codeberg.org`) hosts the repo being cloned; the machine also has a host-keyed systemd-creds backend. ``` cd /some/scratch madtea clone https://codeberg.org/sixfold_space/madtea.git # -> "Cloned https://codeberg.org/sixfold_space/madtea.git from gitea.example ..." (misleading, see below) cd madtea madtea issue list # -> Error: API error 404: not found madtea auth login # reconfigure; flow says "Authenticating to codeberg.org", verifies, "Setup complete! hexajon @ codeberg.org" madtea issue list # -> Error: API error 404: not found (unchanged) madtea auth whoami # -> URL: https://gitea.example / User: claude / Source: repo config / Repository: sixfold_space/madtea (unchanged) ``` The fresh clone is unusable for every repo-scoped API command, and the documented remedy (`auth login`) completes successfully while fixing nothing. The only escape is hand-editing the repo's git config, which no user should need. ## Root cause `internal/service/git/clone.go` resolves credentials via ambient `GetCredentials` (clone.go:120) without ever comparing the resolved forge against the host the user explicitly named in the URL. Three consequences, worst first: 1. **Wrong-forge credential wiring written into the new repo** (clone.go:199-203): `WriteRepoCredentials` stamps `madtea.url = <forge Y>` + `madtea.user = <Y's user>` + the backend marker into a repo whose origin is forge X. A local `madtea.url` is the FIRST link in the ADR 0025 resolution chain (config.go:305, ahead of the origin remote), so this override poisons every subsequent command: they target Y with the owner/repo slug parsed from the X origin (here `sixfold_space/madtea`, which on Y is named differently) -> 404. 2. **auth login cannot repair it**: `RunFirstTimeSetup` correctly derives the forge from origin (setup.go:227-230, ADR 0025 par. 2), authenticates to X, and stores the token host-keyed - but never notices or clears the local `madtea.url` override pointing at Y. The login reports success and changes nothing observable; whoami before and after are byte-identical. 3. **Misleading output**: `CloneResult.ForgeHost` is taken from the ambient forge whenever credentials resolve (clone.go:124-126) - the actual-URL-host branch only runs with no credentials configured at all. So the user is told the clone came "from gitea.example" when git actually fetched from codeberg.org (the clone URL passes through untouched, clone.go:115; the askpass is host-scoped to Y per #1826, so X received no credentials and the clone only worked because the repo is public). Removing the wrong-host override is also all it takes for the existing machinery to work: in a bare dir with only `origin -> codeberg.org` and no local `madtea.url`, the ADR 0025 origin-first chain + host-keyed backend resolve `hexajon @ codeberg.org` perfectly (verified live while diagnosing this). ## Expected - A full-URL clone treats the URL's host as the forge, full stop. Resolve credentials FOR THAT HOST (host-keyed backend / matching config); when none exist, take the #163 unauthenticated public path with its auth note. Never write another forge's `madtea.url`/`madtea.user` into the new clone - either write wiring that matches the origin host or write no URL override at all and let the origin-first chain do its job. - Clone output names the host git actually fetched from. - Defense in depth: origin-derived `auth login` should detect a local `madtea.url` override naming a DIFFERENT host than origin, and repair it (or at least warn loudly) instead of silently succeeding into an unchanged broken state. Split into a follow-up if preferred. ## Related - #97 fixed the shorthand-clone half of this trap (say which forge, warn on mirror=true) - the full-URL path got the display but kept the ambient wiring. - #163 added the unauthenticated public-clone path this case should fall into when the URL's host has no credentials. - #29 is the same one-default-forge assumption on the MCP surface. Observed on v0.15.4-0.20260717031522-615d2732ccad; line numbers from main @ 86e55df6.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#197
No description provided.