Skip to content

fix(gitmodules): stop .claude fetch-recurse from failing pull without trinity-dev creds - #2009

Open
Pat-Lorna-TCC wants to merge 1 commit into
Abilityai:devfrom
Pat-Lorna-TCC:fix/gitmodules-fetch-recurse-submodules
Open

fix(gitmodules): stop .claude fetch-recurse from failing pull without trinity-dev creds#2009
Pat-Lorna-TCC wants to merge 1 commit into
Abilityai:devfrom
Pat-Lorna-TCC:fix/gitmodules-fetch-recurse-submodules

Conversation

@Pat-Lorna-TCC

Copy link
Copy Markdown

Problem

Any clone (or self-hosting Trinity agent, e.g. the bundled trinity-system agent git-syncing this repo) that does not have trinity-dev credentials hits this on every git pull/git fetch:

Git fetch failed: Could not access submodule '.claude'

.gitmodules sets fetchRecurseSubmodules = true for the private .claude submodule, left over from before #1443 when .claude defaulted to update = checkout. true unconditionally tries to recurse-fetch the submodule on every fetch — even when it was never checked out (the update = none default since #1443) — and the fetch attempt fails for anyone without access to Abilityai/trinity-dev.

Confirmed via docker/base-image/agent_server/routers/git.py's pull_from_github, which runs a plain git fetch origin with no submodule flags, so it inherits this setting directly.

Fix

Switch fetchRecurseSubmodules from true to on-demand (git's own default mode) for the .claude submodule:

  • Maintainers who have .claude checked out (opted in per the CLAUDE.md "Development Skills" setup) keep the documented "git pull auto-updates .claude" behavior — on-demand still fetches a populated submodule when the superproject fetch bumps its pointer.
  • Everyone else (OSS clones, self-hosting agents) no longer attempts the fetch at all, since the submodule was never checked out.

Also corrected the one CLAUDE.md line documenting the old true value.

Testing

  • Read the affected code path (agent_server/routers/git.py:pull_from_github) to confirm it's a bare git fetch origin with no override, so the .gitmodules-level setting is the sole determinant.
  • Verified on-demand is git's documented default recursion mode and only recurses into submodules that are populated locally, per git help submodule / git help fetch.

🤖 Generated with Claude Code

… 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.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

@obasilakis obasilakis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 checkoutsubmodule update --initsubmodule.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.

  1. 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 every git 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.

  2. That same population still breaks on any commit that bumps the .claude pointer. With on-demand, git recurses precisely when the superproject fetch moves the gitlink — and then hits the same unreachable URL. On dev that 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 true

That 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.

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.

2 participants