Skip to content

Keep the connector's GitHub commands on https whatever the machine's Git rewrites - #772

Merged
lavaman131 merged 1 commit into
mainfrom
clone-ignores-url-rewrites
Nov 4, 2026
Merged

lavaman131 merged 1 commit into
mainfrom
clone-ignores-url-rewrites

Conversation

@lavaman131

Copy link
Copy Markdown
Collaborator

The connector's Git commands for GitHub now use the repository's https://github.com/ address and the token Chopin issued, whatever the machine's Git configuration, SSH setup or gh login says.

The problem

A machine whose global Git configuration has url.git@github.com:.insteadOf = https://github.com/ (very common) runs the connector as a background service. The connector clones with the issued token over https, but Git rewrote the address to SSH, so the token was never used. A service has no SSH agent, and the work failed with only fatal: Could not read from remote repository., while the same user's own shell worked.

The fix

gitCredentials(file, repository) and githubEnvironment(github, repository) now also pass, through the environment (GIT_CONFIG_COUNT/KEY/VALUE), for that repository only:

  • url.https://github.com/<owner>/<name>.insteadOf and .pushInsteadOf, each mapping the address to itself;
  • the same base for git@github.com:<owner>/<name> and ssh://git@github.com/<owner>/<name>, so a checkout whose origin is an SSH address reaches GitHub over https too.

Both spellings of the owner and name (as Chopin gives them and lowercase) are covered. Nothing is written to the global configuration or to a checkout's own, so the Planner's refusal of a checkout with its own rewrite or credential settings is untouched, and the rest of the person's configuration (identity, signing, other rules) applies as it is.

The Git precedence this relies on, checked with Git 2.43 in the tests

A url.<base>.insteadOf rule rewrites the longest matching prefix. Between two equally long rules the first wins, which is the person's, because command-line and environment configuration come last. So a same-length rule for https://github.com/ does not override them, but a rule for the repository's own address is longer than any rule for the host or the owner. pushInsteadOf is its own list and applies to pushes, so it is set too.

Limits: a person's rule written for exactly this repository's address ties with the new rule and still wins, and other repositories are left as the person has them, since the rules name only the repository the token is for.

Where it applies

  • checkouts.ts: the clone and every later fetch of a repository without a registered checkout.
  • run.ts: the agent's environment (its fetches and pushes), and the fetch of the run's commit into a registered checkout, which used to take the machine's own credentials and so could not reach GitHub from a service. A spike with a registered checkout keeps its old behavior, since a spike never pushes.
  • planner.ts: the Planner command's environment, so its own Git and its agent's keep to https.

When it still goes to SSH

A failure that looks like SSH (Could not read from remote repository, Permission denied (publickey), Host key verification failed) now asks Git how this process's own configuration treats the repository's address, and says so:

Couldn't clone org/repo (fatal: Could not read from remote repository. Git rewrites https://github.com/org/repo.git to git@github.com:org/repo.git (a url.<base>.insteadOf rule in its configuration), and this service has no SSH agent or key to use it with. Remove that rule, or clone the repository over https and give it with --checkout).

Tokens stay masked as before.

Tests

With a real Git binary:

  • the rule semantics: host, owner and repository-length person's rules, equal-length not overriding, lowercase and no .git, other repositories untouched, SSH origins mapped, and pushes;
  • the connector's clone and fetch with a person's rewrite: the address Git is given is the address it uses, a control shows the same configuration rewrites it without the rules, the person's user.name still applies, and the clone's own configuration holds none of it;
  • the agent's fetch and push addresses (run.test.ts) and the Planner command's (planner.test.ts) under a rewrite to SSH;
  • the error that names the rewrite, and silence when nothing rewrites.

The local-GitHub fixture in checkouts.test.ts simulated GitHub with a global insteadOf, which this change correctly neutralizes. A git wrapper on PATH now maps clone and fetch to local bare repositories after logging the address it would have used.

Removing the rules makes the five behavior tests fail. The connector suite passes (272 pass, 0 fail), the new checkouts.test.ts ran 30 times without a failure, and bun run types and bun run ci pass.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline
Assistant-verification: bun test passed: apps/connector, 272 pass 0 fail
Assistant-verification: bun test passed: src/checkouts.test.ts 30 repeats, 0 failures
Assistant-verification: types and lint passed: bun run types and bun run ci
Assistant-verification: mutation check passed: with the rules removed, five behavior tests fail

…Git rewrites

A machine whose Git configuration sends https://github.com/ to SSH (a
url.<base>.insteadOf rule, which is very common) ran the connector's clones
over SSH. The service has no SSH agent, so the token Chopin issued was never
used, and the work failed with only "Could not read from remote repository",
though the person's own shell worked.

The connector's Git commands for a repository now carry, through the
environment, rules that keep that repository's address on https for fetches
and pushes, and map its SSH forms to https. Git rewrites the longest matching
prefix and, between equally long ones, the first, which is the person's; the
repository's own address is longer than any rule for the host or the owner,
so it wins. Nothing is written to the global configuration or a checkout's
own, and the rest of the person's configuration applies as it is. This covers
the clone and fetch of a repository without a checkout, the agent's
environment, the fetch of a run's commit into a registered checkout, and the
Planner command.

When a failure still looks like SSH, the error now says which rewrite did it
and what to do, instead of only Git's fatal line.

The local-GitHub test fixture simulated GitHub with a global insteadOf, which
this change neutralizes by design. A git wrapper on PATH now maps clone and
fetch to local bare repositories after logging the address it would have used.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline
Assistant-verification: bun test passed: apps/connector, 272 pass 0 fail
Assistant-verification: bun test passed: src/checkouts.test.ts 30 repeats, 0 failures
Assistant-verification: types and lint passed: bun run types and bun run ci
Assistant-verification: mutation check passed: with the rules removed, five behavior tests fail
Co-authored-by: Alex Lavaee <lavaman131@github.com>
@lavaman131
lavaman131 force-pushed the clone-ignores-url-rewrites branch from d0e8d28 to b480aa8 Compare November 4, 2026 07:55
@lavaman131
lavaman131 merged commit 2c284a4 into main Nov 4, 2026
24 checks passed
@lavaman131
lavaman131 deleted the clone-ignores-url-rewrites branch November 4, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant