Context
modules init for tracked modules (manifest entries with a <track> column, added in #26) currently does:
git fetch -q origin "$track"
git checkout -q FETCH_HEAD
This intentionally leaves the clone in detached HEAD at the freshly-fetched tracking ref. The consequence: any consumer that wants to edit and push from the clone has to do a follow-up "make it editable" dance themselves (typically in a welcome task or similar).
That dance has a subtle footgun. Reproduced today in baby-joel/home's welcome task:
modules init detaches at origin/main tip — good.
- Welcome sees detached HEAD and runs
git checkout main to put the clone on a real branch.
- The local
main branch ref is stale — it points at the previous session's tip, not the freshly-fetched one.
- Net effect:
git checkout main regresses HEAD by N commits. The drift detector then warns "N commits available — run modules init to pull", even though modules init literally just ran.
The fix on the consumer side is one line — git checkout -B main origin/main — but the failure mode is invisible by default (you only notice if you happen to compare HEAD before and after, or if you have a drift warning that contradicts the just-pulled state). Anyone copying the welcome pattern is at risk.
Proposal
When a manifest entry has a <track> ref, modules init should leave the clone on a real branch tracking the remote, not detached at FETCH_HEAD.
Concretely, replace:
git fetch -q origin "$track"
git checkout -q FETCH_HEAD
with something like:
git fetch -q origin "$track"
git checkout -B "$track" "origin/$track" -q
# (optionally: git branch --set-upstream-to "origin/$track" "$track")
Semantics:
- Reproducibility unchanged: tracked clones still end up at exactly
origin/<track> tip.
- Editability becomes the default for tracked modules: the clone is push-ready out of the box; consumers don't need a follow-up dance.
- Untracked (purely pinned) modules still detach at the pin — no behavior change.
This matches the spirit of the original tracking design (#19): "freshness layer for gitignored local clones." If the clone is meant to be the editable view of a tracked repo (which is the emerging pattern for home repos using den/fold via modules), making it editable is the obvious default.
Why this is the right layer
Right now every home that wants to edit through modules/ has to reimplement the same checkout dance, and any subtle bug in that dance (like the one above) propagates. Today only baby-joel/home does the dance — but per recent house decisions we're migrating all agent homes to treat modules/{den,fold} as the canonical editable clone (replacing the old ~/agents/<name>/{den,fold} workspace clones). That means every home is about to need the dance, which means the dance should not exist.
Consumers that explicitly want detached state (e.g. CI provisioners that don't push) lose nothing: they were already on the tip; being on a real branch ref doesn't impose any operation, and they can still git checkout --detach HEAD if they want.
Verification approach
- BATS coverage: extend the
init tests in test/init.bats to assert that after modules init on a tracked module, git symbolic-ref HEAD returns refs/heads/<track> and HEAD matches origin/<track>.
- Regression test: stale local
<track> branch (pointing at an older SHA) — after modules init, local <track> should be advanced to origin/<track> (this is exactly the scenario that regresses today on the consumer side).
Out of scope
- Whether to
--set-upstream automatically — separable; safe default but worth its own line of discussion.
- Whether untracked modules should also stop detaching — no, pin reproducibility relies on detach.
Happy to PR this if the direction lands.
Related
Context
modules initfor tracked modules (manifest entries with a<track>column, added in #26) currently does:git fetch -q origin "$track" git checkout -q FETCH_HEADThis intentionally leaves the clone in detached HEAD at the freshly-fetched tracking ref. The consequence: any consumer that wants to edit and push from the clone has to do a follow-up "make it editable" dance themselves (typically in a
welcometask or similar).That dance has a subtle footgun. Reproduced today in
baby-joel/home's welcome task:modules initdetaches atorigin/maintip — good.git checkout mainto put the clone on a real branch.mainbranch ref is stale — it points at the previous session's tip, not the freshly-fetched one.git checkout mainregresses HEAD by N commits. The drift detector then warns "N commits available — runmodules initto pull", even thoughmodules initliterally just ran.The fix on the consumer side is one line —
git checkout -B main origin/main— but the failure mode is invisible by default (you only notice if you happen to compare HEAD before and after, or if you have a drift warning that contradicts the just-pulled state). Anyone copying the welcome pattern is at risk.Proposal
When a manifest entry has a
<track>ref,modules initshould leave the clone on a real branch tracking the remote, not detached atFETCH_HEAD.Concretely, replace:
git fetch -q origin "$track" git checkout -q FETCH_HEADwith something like:
Semantics:
origin/<track>tip.This matches the spirit of the original tracking design (#19): "freshness layer for gitignored local clones." If the clone is meant to be the editable view of a tracked repo (which is the emerging pattern for home repos using den/fold via modules), making it editable is the obvious default.
Why this is the right layer
Right now every home that wants to edit through
modules/has to reimplement the same checkout dance, and any subtle bug in that dance (like the one above) propagates. Today onlybaby-joel/homedoes the dance — but per recent house decisions we're migrating all agent homes to treatmodules/{den,fold}as the canonical editable clone (replacing the old~/agents/<name>/{den,fold}workspace clones). That means every home is about to need the dance, which means the dance should not exist.Consumers that explicitly want detached state (e.g. CI provisioners that don't push) lose nothing: they were already on the tip; being on a real branch ref doesn't impose any operation, and they can still
git checkout --detach HEADif they want.Verification approach
inittests intest/init.batsto assert that aftermodules initon a tracked module,git symbolic-ref HEADreturnsrefs/heads/<track>and HEAD matchesorigin/<track>.<track>branch (pointing at an older SHA) — aftermodules init, local<track>should be advanced toorigin/<track>(this is exactly the scenario that regresses today on the consumer side).Out of scope
--set-upstreamautomatically — separable; safe default but worth its own line of discussion.Happy to PR this if the direction lands.
Related
<track>column.baby-joel/homewelcome task (private repo; one-linegit checkout -Bchange as described above).