fix(gitmodules): stop .claude fetch-recurse from failing pull without trinity-dev creds - #2009
Conversation
… trinity-dev creds fetchRecurseSubmodules = true unconditionally tries to recurse-fetch the private .claude submodule on every git fetch/pull, even when it was never checked out (update = none default since Abilityai#1443). Any clone or self-hosting agent without trinity-dev credentials hits: Git fetch failed: Could not access submodule '.claude' Switch to on-demand: it still auto-updates .claude for maintainers who have it checked out (a submodule-bump commit triggers the fetch, preserving the documented "handles git pull automatically" behavior), but stops the fetch attempt for everyone who doesn't have it initialized.
|
Resolve by running |
obasilakis
left a comment
There was a problem hiding this comment.
Approving — the mechanism is right, and I verified the thing that most needed verifying: this does not break the core-team opt-in path documented in CLAUDE.md → "One-time setup after cloning". Thanks for the clear problem statement; the pointer to agent_server/routers/git.py made this quick to confirm.
I did reproduce everything against a synthetic superproject on git 2.50.1 rather than take the reasoning on faith, and it turned up one nuance worth recording before this lands.
Confirmed
The reported failure is real and reproduces exactly. Bare git fetch origin at docker/base-image/agent_server/routers/git.py:1300, surfaced at :1308 as Git fetch failed: {stderr} — which is the message in your issue:
CONFLICT-FREE FIXTURE, submodule URL made unreachable ("no trinity-dev creds")
fetchRecurseSubmodules=true, .claude/ non-empty -> "Could not access submodule 'sub'" EXIT=1
The core-team path survives. Opting in exactly as CLAUDE.md documents (submodule.<name>.update checkout → submodule update --init → submodule.recurse true), then bumping the pointer upstream:
maintainer git pull -> EXIT=0
Fetching submodule sub
submodule advanced 050fa84 -> 7dcd891
So "handles git pull automatically" holds, and your CLAUDE.md wording ("once .claude is checked out — a submodule-bump commit triggers the fetch") is accurate.
Consistency bonus: src/backend/enterprise carries no fetchRecurseSubmodules at all, so it already runs on git's on-demand default. This brings .claude in line with its sibling rather than making the two diverge.
The one gap — the fix is incomplete for exactly the population it targets
Full matrix, all measured:
fetchRecurseSubmodules |
submodule pointer | .claude/ on disk |
git pull |
|---|---|---|---|
true |
unchanged | empty | exit 0 |
true |
unchanged | non-empty | exit 1 ← the reported bug |
true |
moved | empty | exit 0 |
on-demand |
unchanged | non-empty | exit 0 ← fixed by this PR |
on-demand |
moved | empty | exit 0 |
on-demand |
moved | non-empty | exit 1 ← residual |
Two things follow.
-
A pristine clone was never affected. An empty submodule directory is the "not initialized" case git treats as normal — it never errors, under either setting. So the failure's entire population is clones where
.claude/has become non-empty-but-not-a-repo: a self-hosting agent running Claude Code in the repo root (which writes.claude/settings.local.json), a half-initialized checkout, restored state. That makes the PR body's "any clone … hits this on everygit pull" a bit broader than what actually happens — worth tightening, since it's the sentence a future reader will use to decide whether they're affected. -
That same population still breaks on any commit that bumps the
.claudepointer. Withon-demand, git recurses precisely when the superproject fetch moves the gitlink — and then hits the same unreachable URL. Ondevthat is not rare:
$ git log origin/dev --since=30.days --oneline -- .claude | wc -l
6
Six in the last 30 days, nine in 90.
The complete fix is fetchRecurseSubmodules = false. Since update = none already means the submodule is never checked out without an explicit opt-in, maintainers would add one more line to the setup block they already run:
git config submodule..claude.update checkout
git config submodule..claude.fetchRecurseSubmodules on-demand # <- new
git submodule update --init .claude
git config submodule.recurse trueThat is config-first in the same shape update = none already uses, and it takes the failure to zero for everyone without credentials.
Recommendation
Land this as-is — it is strictly better than true in every cell of that matrix and carries no regression risk — and treat false as a follow-up rather than a blocker. I'm happy to file the follow-up issue with the matrix above if you'd rather not carry it.
One note for whoever merges: .gitmodules is read from the working tree, so an already-affected clone only gets the benefit after it successfully pulls this commit. git pull aborts on the failing fetch, so those users need git fetch origin; git merge origin/dev (the fetch error is partial — refs still update), or a one-shot git -c submodule..claude.fetchRecurseSubmodules=no pull. Worth a line in the release notes.
Problem
Any clone (or self-hosting Trinity agent, e.g. the bundled
trinity-systemagent git-syncing this repo) that does not havetrinity-devcredentials hits this on everygit pull/git fetch:.gitmodulessetsfetchRecurseSubmodules = truefor the private.claudesubmodule, left over from before #1443 when.claudedefaulted toupdate = checkout.trueunconditionally tries to recurse-fetch the submodule on every fetch — even when it was never checked out (theupdate = nonedefault since #1443) — and the fetch attempt fails for anyone without access toAbilityai/trinity-dev.Confirmed via
docker/base-image/agent_server/routers/git.py'spull_from_github, which runs a plaingit fetch originwith no submodule flags, so it inherits this setting directly.Fix
Switch
fetchRecurseSubmodulesfromtruetoon-demand(git's own default mode) for the.claudesubmodule:.claudechecked out (opted in per the CLAUDE.md "Development Skills" setup) keep the documented "git pullauto-updates.claude" behavior —on-demandstill fetches a populated submodule when the superproject fetch bumps its pointer.Also corrected the one CLAUDE.md line documenting the old
truevalue.Testing
agent_server/routers/git.py:pull_from_github) to confirm it's a baregit fetch originwith no override, so the.gitmodules-level setting is the sole determinant.on-demandis git's documented default recursion mode and only recurses into submodules that are populated locally, pergit help submodule/git help fetch.🤖 Generated with Claude Code