[BUG] Desktop app's embedded git sync re-fetches all remote tags, bypassing `remote.<name>.tagOpt --no-tags`

Status Open
Reported on v2.1.260
Maintainer reply None cached
Activity 0 comments · opened Sep 7, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

While the Claude desktop app is open on a repository, an in-process (embedded) git client — apparently the cloud-session ref sync (fetchOutcomeBranches / [runner:session] in the binary) — periodically fetches all remote tags, ignoring the repository's remote.origin.tagOpt --no-tags setting.

Our monorepo's origin carries ~65,000 CI-generated tags (per-PR builder/checker merge tags). The sync re-creates all of them as loose ref files on every app refresh cycle (startup / window focus), landing ~30-45 s after the app's visible git.exe background fetch. 65k loose refs under .git/refs/tags/ make every ref enumeration crawl on Windows/NTFS (a 20-second git log --tags, sluggish git GUIs), and any manual cleanup is silently undone within the hour.

The user cannot work around it:

  • There is no setting or environment variable to opt out of background fetching, in the app or in the docs.
  • remote.origin.tagOpt --no-tags is ignored (the embedded client does not consult git config).
  • No git.exe is spawned, so wrappers, hooks (core.hooksPath), and credential-helper interception never see it.
  • The only mitigation we found is a filesystem ACL denying file creation under .git/refs/tags/.

Note: the other background fetch the app runs (worktree/default-branch freshness) is well-behaved — it spawns git.exe, names the remote, and so respects tagOpt. Only the embedded sync misbehaves.

What Should Happen?

Preferred: a setting to opt out of all background fetches (per repository or globally) — both the embedded session-ref sync and the spawned default-branch freshness fetch. Some teams have policies against unattended fetches, and Claude Code currently offers no way to comply: the app fetches on its own schedule regardless of user intent, and features that depend on a fetch could simply fall back to the locally cached refs (as the worktree documentation already describes for the failure case).

Independently of the opt-out, the tag behavior is a bug in its own right: the embedded sync should fetch only the refs it needs and skip tags (the equivalent of --no-tags), matching the app's own spawned fetch path, which already passes --no-tags --no-recurse-submodules --no-write-fetch-head --no-auto-gc — or at minimum respect remote.<name>.tagOpt.

Error Messages/Logs

There are no error messages — the sync is silent. Forensic traces instead:

Tag floods leave **no reflog entries, no FETCH_HEAD write, and no new packs**; ~65k loose ref files are written over ~60 s (per-ref writes, not git's batched ref transaction):


$ ls --time-style=full-iso -lt .git/refs/tags/   # one flood event
drwxr-xr-x ... 2026-09-07 14:30:03.574 release
drwxr-xr-x ... 2026-09-07 14:30:01.382 release-candidate
drwxr-xr-x ... 2026-09-07 14:29:01.183 prbuilder-merge      <- ~62 s of ref writing


A WMI process-creation watcher on `git.exe` captured every git process during multiple flood events. The only background fetch ever spawned is the (innocent) default-branch freshness fetch:


git.exe -c core.hooksPath=\\.\NUL -c safe.directory=* -c core.fsmonitor=false
  -c branch.autoSetupMerge=false -c diff.ignoreSubmodules=dirty -c diff.submodule=short
  -c status.submoduleSummary=false -c submodule.recurse=false -c gc.auto=0 -c maintenance.auto=false
  fetch --no-recurse-submodules --no-write-fetch-head origin +refs/heads/master:refs/remotes/origin/master


Canary timing (repeated 3x): delete one tag -> app focus event -> spawned git.exe fetch at T+0 -> canary re-appears at T+30..45 s with no git.exe activity in between (e.g. focus fetch 16:17:04, canary ref file mtime 16:17:36).

Steps to Reproduce

  1. Clone a repo whose remote has a very large number of tags (tens of thousands; any repo with per-build CI tags works). Full clone, tags present.
  2. git config remote.origin.tagOpt --no-tags
  3. Delete the local tags (git for-each-ref --format="delete %(refname)" refs/tags | git update-ref --stdin), then git pack-refs --all.
  4. Open the Claude desktop app on this repo (a session associated with the repo; we observed it with cloud/session features untouched at defaults).
  5. Give the app window focus and wait ~1 minute.
  6. git for-each-ref refs/tags | wc -l — all remote tags are back as loose refs, despite tagOpt. Control: replaying the spawned git.exe fetch command from step 5's process listing verbatim does not restore them; only the app's embedded sync does.

Claude Model

Other

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

1.46388.1.0

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

  • Claude desktop app 1.46388.1.0 (Windows Store) with Claude Code CLI 2.1.260; also observed with the 2.1.25x line since ~Sep 4. The August line (~2.1.22x) only ran the well-behaved spawned fetch and caused no floods once tagOpt was set.
  • Windows 11 Pro 10.0.26200, Git for Windows 2.52.0.windows.1.
  • Strings in claude.exe associated with the suspected code path: fetchOutcomeBranches, [runner:session] fetched N outcome branch(es), CLAUDE_CODE_SYNC_SESSION_REFS.
  • The canary tag used points at a CI merge commit not reachable from the default branch, so ordinary tag auto-follow of a branch fetch cannot explain its return — the sync fetches tags wholesale.
  • Impact scales with org size: CI tag storms of this magnitude are common in large monorepos, and the flood re-applies after every cleanup, degrading all local git tooling.

View original on GitHub ↗