Skip to content

Shared-Mirror Remote Pollution — git remote add Inside a Checkout Is Permanent, Not Task-Scoped

Applies to: Anyone adding a git remote (or fetching from a path outside origin) inside a Multica-managed checkout to pull in another task's branch.

The trap

Every Multica-managed checkout for this repo is a worktree attached to one shared bare-clone mirror on the host — worktrees share that mirror's common ref store and config. A git remote add <name> <path> run inside a task's worktree doesn't stay scoped to that task: it writes into the shared mirror's config, permanently, and every later checkout on that host inherits it. The same is true of git fetch <path-to-another-workdir> even with no remote added — either one pulls another task's ephemeral workdir into shared, durable state.

What actually happened

Root-caused from the 2026-08-11 checkout brownouts (OBJ-2449): the shared enkidu mirror on joshuas-mbp-2 had accumulated 44,520 refs vs 682 on origin. Twelve remotes had been added over time from inside agent task worktrees, each pointing at another task's ephemeral workdir:

  • codi-fix
  • codi-fix-116
  • codi-phase116
  • codi-source
  • codi-workspace3
  • codi-worktree
  • codi_fix
  • codi_workspace
  • dori-eced1e24
  • dori-local
  • dori-workspace
  • orion-workspace

Each git remote add + git fetch from inside a task dumped thousands of remote-tracking refs (and ultimately ~40k reflog files) into the shared cache. That bloat made the daemon's per-cycle reflog expire --all / gc time out every cycle (OBJ-2476, filed as a companion ticket) and browned out checkouts for 20–40 minutes every ~2 hours.

2026-08-19 status update and the real failure loop

OBJ-2739 re-opened this: a routine audit of the enkidu mirror found 12 of the same junk remotes still configured, carrying 35,407 of 38,183 total refs (92.7%) — the "already performed" cleanup this doc used to claim (see the corrected Cleanup section below) had not actually happened, or had run without git remote remove. The daemon's daemon.log showed every git maintenance cycle failing, ~2h apart, exactly as before.

Two things came out of that investigation that change the model in this doc:

  1. The mirror-level cleanup is now real and script-backed, not a one-time manual pass — see Cleanup and The enforced guard below.
  2. A stale ref lock is not an independent contributor to the brownouts — it's downstream residue of the daemon's own timeout handling, regenerated every cycle. The daemon runs git maintenance (reflog expire --expire=30.days --all and gc --prune=30.days) under a 30-second timeout, enforced with SIGKILL. On a mirror with hundreds of live worktrees, reflog expire --all routinely runs past 30s under real concurrency/I/O load (confirmed: a quiet-host dry run of the identical command completed in ~7s, so ref/reflog volume alone isn't what pushes it over — concurrent worktree traffic and disk pressure are). SIGKILL gives git no chance to release the ref lock it was holding, so every timeout stamps a fresh stale .lock file, one per cycle, and that stranded lock then breaks the next cycle's operations on that same ref — which is what made the lock look like a separate, independently-occurring problem rather than a symptom of the same loop. The loop is: daemon starts maintenance → hits the 30s timeout on a busy mirror → SIGKILL → git can't release its ref lock → next cycle's gc fails on that stale lock → repeat. Pruning refs breaks the reflog-volume half of this (see the before/after numbers below), but cannot stop the SIGKILL/stale-lock half on its own — that's a daemon-side bug, filed separately as OBJ-2759, outside this repo's scope.

The practical upshot: after the OBJ-2739 cleanup, the user-visible symptom (repo checkout failed in the daemon log) stopped — 6 occurrences in the 16h before the cleanup, 0 since. A single fully clean git maintenance cycle (no reflog expire timeout at all) is not a realistic bar to hold this doc to until OBJ-2759 lands; judge recurrence by repo checkout failed, not by a zero-warning daemon log.

The rule

Never git remote add inside a Multica-managed checkout, and never git fetch <path> pointing at another task's working directory. If you need another branch, it's on origin — fetch it there:

git fetch origin <branch>

If you find yourself reaching for a second remote, that's a scope mismatch, not a workaround — hand back to Maggie rather than adding it. This rule is now codified in the multica-handoff-protocol workspace skill's "Git remotes — origin only, never another task's workdir" section (OBJ-2477), injected into every crew agent run.

Symptom if you hit this anyway

  • git remote -v inside a Multica checkout shows entries beyond origin.
  • git count-objects -v or the mirror's ref count is wildly out of proportion to origin's (compare against git ls-remote origin | wc -l).
  • The daemon's gc cycle times out or checkouts brown out for extended windows with no other explanation.

Cleanup, if the mirror is already polluted

Correction: this section previously stated the host-side cleanup for OBJ-2477 "had already been performed." It had not — the OBJ-2739 audit on 2026-08-19 found all 12 remotes named above still configured, still carrying 35,407 of 38,183 refs (92.7%) on the enkidu mirror. Either the original cleanup never ran, or it ran without git remote remove. Either way, don't trust a doc claim of "already cleaned" over a fresh --audit run.

The supported cleanup path is now scripts/multica-mirror-hygiene.mjs (added under OBJ-2739), not a hand-run sequence of git remote remove / reflog expire / gc:

bash
# See what a full cleanup would do, without touching anything
node scripts/multica-mirror-hygiene.mjs --prune ~/multica_workspaces/.repos/<workspace>/<mirror>.git

# Actually clean it up (only against a quiesced mirror — see the script's own warning)
node scripts/multica-mirror-hygiene.mjs --prune ~/multica_workspaces/.repos/<workspace>/<mirror>.git --yes

--prune --yes removes every remote not on the allowlist (origin only) and its refs, clears stale ref locks, expires reflogs, then runs a single git gc --prune=now — so only run it against a quiesced mirror (no in-flight checkouts, daemon stopped), the same precaution the old manual steps required. Full mode reference: scripts/README.md#multica-mirror-hygienemjs.

Measured before/after, enkidu mirror (the ref-level work — removing the 12 junk remotes and clearing 2 stale ref locks — landed via Codi's OBJ-2739 verification pass on 2026-08-19; the git gc --prune=now object-level reclaim was intentionally deferred to a genuinely quiet window since it wasn't needed for brownout relief — see the note below the table):

MeasureBefore (2026-08-19 14:03 UTC)After (2026-08-19 14:45 UTC)
Total refs38,1832,796
Junk-remote refs35,407 (92.7%)0
refs/remotes/origin/*683686
refs/heads/*1,6881,703
packed-refs lines38,3292,983
Reflog files33,5112,115
Configured remotes13origin only
Stale .lock files2 (6 days old)0
On-disk size3.2G3.2G (object-level gc --prune=now not yet run)
Registered worktrees332344

The longwoodlabs-iac mirror on the same host had the same problem — 3 junk remotes, 392 → 196 refs after the same cleanup. This trap is not enkidu-specific — it hits every repo mirror on a shared checkout-daemon host. If you're cleaning up a polluted mirror for a different repo, run --audit on it first rather than assuming enkidu's numbers apply.

Why the on-disk size didn't drop yet: --prune --yes's git gc --prune=now step is the one piece of Task 2 (the full OBJ-2739 host operation) still outstanding as of this writing — it needs a window with the daemon stopped and zero in-flight agent tasks (332+ worktrees are registered against this mirror; an unreachable object belonging to a live task's branch could otherwise be pruned). The ref-level cleanup above is what actually stops the brownouts (see the 2026-08-19 status update above) — the deferred gc is disk reclaim, not brownout relief, worth doing in its own quiet window rather than urgently.

The enforced guard: script and hourly sweeper

The rule above ("never git remote add inside a checkout") was previously enforced by nothing but the rule itself — a single careless git remote add could silently re-bloat the mirror with no guard catching it. OBJ-2739 added one.

  • scripts/multica-mirror-hygiene.mjs --sweep --yes — the cheap recurring subset of --prune: removes non-allowlisted remotes and clears stale ref locks (default threshold 60 minutes). Deliberately never runs gc, so it's safe to run against a mirror with live worktrees attached — unlike --prune, it doesn't need a quiesced host.
  • Cadence: hourly, via a user-level LaunchAgent (infra/multica-mirror-hygiene/com.objectuve.multica-mirror-hygiene.plist, StartInterval 3600s).
  • Install: bash scripts/install-mirror-hygiene-agent.sh — idempotent, re-running it reloads the agent rather than erroring.
  • Uninstall:
    bash
    launchctl unload ~/Library/LaunchAgents/com.objectuve.multica-mirror-hygiene.plist
    rm ~/Library/LaunchAgents/com.objectuve.multica-mirror-hygiene.plist
  • Logs: ~/.multica/mirror-hygiene.log.
  • Scope: the same ~/multica_workspaces/.repos/**/*.git-only guard as every other mode (assertInScope) — it cannot touch a human's own IDE checkout (e.g. ~/code/enkidu).
  • Status as of this writing: implemented and tested, but not installed on the host. It was loaded once during OBJ-2739 verification, confirmed working, then deliberately unloaded — installing it for real is a pending Josh/Maggie decision, not something the OBJ-2739 merge did automatically. Until it's installed, nothing is clearing stranded ref locks between daemon cycles.
  • It also incidentally mitigates the SIGKILL/stale-lock loop described above: since a daemon-stranded lock is just a fresh file past the sweeper's staleness threshold, an hourly --sweep clears each one well within the ~2h gap before the next gc cycle would otherwise trip on it again — real mitigation for as long as OBJ-2759 (the daemon-side bug) stays open, not a fix for it.

Why not fix it at the platform level instead

A daemon-side guardrail — stripping or rejecting non-origin remotes at multica repo checkout, or having the gc cycle prune them — would be the more durable fix, since an unenforced rule can be forgotten while a daemon that strips non-origin remotes cannot. That's Multica daemon/CLI behavior, not Enkidu app code, so no crew agent owns it directly; it was escalated to Josh as a separate ask (OBJ-2477 comment thread) rather than absorbed into this doc's scope. The hourly sweeper above is a host-side workaround that bounds the damage to ≤1 hour of re-accumulation — it does not stop an agent from re-polluting the mirror in the first place, and it cannot fix the daemon's 30-second-timeout/SIGKILL behavior that OBJ-2759 tracks separately. Until the daemon strips non-origin remotes at checkout time, this doc's rule and the sweeper together are the only things standing between a careless git remote add and a repeat of this incident.

  • OBJ-2449 — the checkout-brownout symptom this root-caused.
  • OBJ-2476 — companion ticket, the daemon gc-failure escalation caused by the same ref bloat.
  • OBJ-2477 — the original prevention effort: the crew-guidance rule and this doc.
  • OBJ-2739 — the 2026-08-19 re-audit and cleanup: found the OBJ-2477 cleanup hadn't actually happened, shipped scripts/multica-mirror-hygiene.mjs (--audit/--prune/--sweep) and the hourly sweeper, corrected the stale-lock causality model.
  • OBJ-2759 — the daemon-side bug this doc's causality correction points at: the 30s-timeout SIGKILL that strands a ref lock every git maintenance cycle. Not fixable from this repo.
  • OBJ-2757 — separately filed: rotation of a plaintext Gitea credential found embedded in a zero-ref junk remote's URL (longwoodlabs-iac mirror's origin-ip) during the OBJ-2739 audit. Not a shared-mirror-pollution issue itself, but found via this same investigation.
  • shared-workdir-scratch-collision-gotcha.md — sibling gotcha, same "shared host-level git state outlives a single task" root cause, hit from the scratch-file/fixed-path side instead of the remote side.
  • repo-checkout-origin-only-gotcha.md — sibling gotcha, same shared-mirror surface, hit from the "checkout can't see a local-only branch" side instead of the remote-pollution side.
  • multica-workspace-skills.md — the multica-handoff-protocol skill body, including the "Git remotes — origin only" section.
  • multica-agent-crew.md — the crew instructions this rule lives in (Crew-wide rules, rule 9).
  • scripts/README.md — full mode reference for multica-mirror-hygiene.mjs and the installer script.

Last updated: 2026-08-19 (OBJ-2739: corrected the false "cleanup already performed" claim, recorded measured before/after numbers, documented the script + hourly sweeper, and corrected the stale-ref-lock causality model)

Loading…