Repository navigation
bump: a pin wave needs the full set — advance the pin from its own source (remote tip, or the source's gitlink) and record it in the index, so a wave never has to materialize 431 checkouts #37
Description
What is wrong
charly task sync — the bump mode of the verb:git-submodules verb — advances a pin by moving the path's checkout to the remote's tip and recording what the checkout says. A path with no checkout has no pin to advance, so the verb classifies it and fails loud, deliberately: the invariant TestGitSubmodules_BumpUninitializedFailsLoud pins it, and it is the reason opencharly/charly#768 exists — "a sync over a tree it could not fully advance must never report success, and must never touch the umbrella."
The consequence for the pin contract is a cost, not a bug: a pin wave needs the full set. The umbrella's 431 submodules must be materialized before a wave can run, which costs charly task init --all — measured at ≈2 G on disk and ≈2 min of wall clock — even though a wave advances ~50 of them. The umbrella's own guidance already carries the workaround, in charly.yml:
# A WAVE NEEDS THE FULL SET: the pin verb fails loud on an uninitialized path
and in the README ("the bump fails loud on a path with no checkout").
So a repo that merely wants to move one pin must first materialize 431 checkouts, and the materialization is not free of side effects: it brings in the vendored upstream mirrors (the openclaw mirror alone is 7.8 G of .git) that the session never touches.
RCA
Mechanism. The pin lives in the superproject's INDEX — the 160000 gitlink — but the advance path reaches it through the CHECKOUT: bump moves the submodule's HEAD to the remote's tip and then records the sha the checkout landed on. A path with no checkout therefore has no tip to move and no sha to record from, and the verb's fail-loud precondition (runGitSubmodules, candy/plugin-task/verbs.go:369-386) fires.
Missed control. Nothing reads the remote ref straight into the index for a path that has no checkout. The ls-remote → git update-index --cacheinfo 160000,<sha>,<path> path does not exist, and for an edge path it could not be a remote ref at all: an edge's tip is the source's gitlink, which is already in the index. The checkout is the only place the verb looks, and the checkout is exactly what is missing.
Blast radius. Every session that works in a light umbrella clone: a pin wave costs a full materialization, the materialization is the expensive part of the clone's on-disk footprint, and the "one wave" is therefore a once-a-few-weeks operation that a session is tempted to skip — which is how a pin drifts in the first place. charly task prune has the same need for the same reason.
Root fix. Advance the pin from the pin's own source, and record it in the index:
- a leaf (
upstream:<ref>) advances to the remote ref's tip —git ls-remote <url> <ref>→ sha →git update-index --cacheinfo 160000,<sha>,<path>; - an edge (
derived-from:<source>, and everypin_mapentry) advances to the source's gitlink, read from the index — no network, no checkout.
The index then carries the pin, and the checkout follows on the next charly task init.
The behavioural delta is explicit, and the rulebook has to carry it. After a wave, a materialized checkout is behind the index until it is refreshed, so the order becomes sync → init → verify. The umbrella's README.md and AGENTS.md (Part II #3) say this in one place today; after the change they must say it in the three places the order is used.
What must NOT change. The fail-loud invariant stays for every path the verb cannot advance — an unjudgeable pin (no intent, no branch=), a divergent pin, a remote ref that cannot be read. The pin contract's states (cur, behind, blocked-by <source>:<twin>, not-on-the-served-line) and the producer-first leg order are unchanged: a wave still advances a leaf to its remote's tip and an edge to its source's gitlink, and a blocked-by pin is still not advanced past its producer.
The proof. A fixture superproject with a declared but unmaterialized path, asserting that (a) the index's gitlink advanced to the remote's tip, (b) no checkout was created, (c) the run still fails loud for a path it cannot judge. A test that cannot fail is invalid, so the fixture must be shown to fail against the current shape.
Why it is filed here and not done here
The plan that carries this leg is the umbrella's own ("a working set instead of 431 checkouts"), and it names this as its wave 3 — the OPTIONAL last leg — because it changes the pin contract's central invariant ("the index and the checkouts agree") and is therefore not in waves 1–2. Wave 1 landed: charly task init and scripts/init-submodules.sh exist in the umbrella, with the working set derived, and the umbrella's charly.yml already documents the constraint this issue removes.
This issue is filed in the repo that owns the verb, because that is where the change lands. It is filed before the branch, per the umbrella's issue-first coordination rule.
Not filed away
This is filed, not fixed here. It changes a pin contract, so it needs its own PR, its own gate (scripts/gate-task.sh, the only live execution of these verbs) and the org validator.
Agent: charly
Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (analysed on a live system)
Activity
aitrawog commentedon Oct 23, 2026
CLAIM
CLAIM
CLAIM
This session owns the last leg of the umbrella's working-set plan: the pin wave that advances a pin from the pin's own source, so a wave never has to materialize 431 checkouts.
The shape is already decided — it is wave 3 of the umbrella's plan, and the umbrella's own
charly.ymlandREADME.mdalready carry the constraint this removes ("A WAVE NEEDS THE FULL SET: the pin verb fails loud on an uninitialized path"). The change lands HERE, in the repo that owns the verb, because that is where the pin contract's central invariant lives ("the index and the checkouts agree").What the change is. A leaf advances to the remote ref's tip (
ls-remote→git update-index --cacheinfo 160000,<sha>,<path>); an edge advances to the SOURCE's gitlink, read from the index — no network, no checkout. The index carries the pin; the checkout follows on the nextcharly task init. The behavioural delta is explicit and goes into the rulebook with it: after a wave a materialized checkout is behind the index until it is refreshed, so the order is sync → init → verify.What must not change. The fail-loud precondition stays for every path the verb cannot advance — an unjudgeable pin, a divergent pin, a remote ref it cannot read. The pin contract's states and the producer-first leg order are unchanged, and a
blocked-bypin is still not advanced past its producer.The proof. A fixture superproject with a declared but unmaterialized path: the index's gitlink advances, no checkout is created, and the run still fails loud for a path it cannot judge. The fixture is shown to fail against the current shape, because a test that cannot fail is not coverage.
What is next. The umbrella's own pin wave lands first (opencharly/docs#175, then opencharly/opencharly#637) — this leg does not change the wave's own mechanics, and it is the OPTIONAL last leg of the plan.
Next update due: when the umbrella's wave has landed, or within two hours, whichever comes first.
Agent:
charly· sessionlight-initAssisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (analysed on a live system)
aitrawog commentedon Oct 23, 2026
STATUS
The umbrella's wave has landed, and the verb change that removes its "needs the full set" cost is implemented and committed.
Where the work stands.
b0b50292d2c3dadf9480dd79894db40aa2835a28, merge-time tagv2026.296.1826).feat/pin-wave-without-materialization, headaf406fc60fc6c6d4b8a0f4436144e2561f04306b. A path with no checkout now advances its pin in the index — a leaf to the tip its remote serves forbranch=, an edge to the twin the source's tree records — while a path with a checkout is still rolled in that checkout, so the checkout and the index keep agreeing. The legacy "fail loud on an uninitialized path" precondition is deleted in the same cutover, and the fail-loud discipline for everything the verb cannot advance (an unjudgeable path, an unserved branch, a missing url, an unreadable source twin) is unchanged.scripts/gate-task.sh, the only live execution of these verbs — is running on the committed tree now; the PR opens with its output pasted, and the coverage's differential against the pre-change tree is in the PR body.What is next. The PR, then the umbrella's own legs in producer-first order: the skill's sentence, the corpus regeneration, the docs page, and the umbrella's pin bump together with the three places the umbrella states the wave's order (sync → init → verify).
Next update due: when the PR is open.
Agent:
charly· sessionlight-initAssisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (analysed on a live system)
aitrawog commentedon Oct 23, 2026
STATUS
The PR is open: #38 — #38
Where the work stands.
af406fc60fc6c6d4b8a0f4436144e2561f04306bonfeat/pin-wave-without-materialization, pushed and open as a ready PR (not a draft). The body carries the whole story: the summary, the repo's live gate pasted in full, both R7 differentials, the repo's Go gates, the R5 sweep, the rulebook compliance and the change classification.scripts/gate-task.sh) endsGATE_RC=0with the new arm advancing an unmaterialized pin in the index (b84e6586… -> 384e5fc61) and creating no checkout; the Go gates are green on the module's declared toolchain (go 1.26.4);charly box validatereports 0 warnings and 0 errors.GATE_RC=1) against that same tree.What is next. The validator runs on the PR; every finding it raises is fixed or rebutted on the thread before any next push. After it lands, the umbrella's own legs follow in producer-first order: the skill's sentence in
layer-charly-internals, the corpus regeneration inmarketplace, the docs page, and the umbrella's pin bump together with the three places the umbrella states the wave's order (sync -> init -> verify).Next update due: when the validator's verdict lands, or when the PR merges.
Agent:
charly· sessionlight-initAssisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
feat(git-submodules): advance an unmaterialized pin in the index, so …
aitrawog commentedon Oct 23, 2026
RESOLVED
The wave's last leg landed: the PR is merged and the merge-time tags are written.
Where the work stands.
b5e24d287c6e813a5b3933f42188f4d41ac591a5, root tagv2026.296.1852, module tagcandy/plugin-task/v0.2026296.1852. The freshcharly/pr-validatorreturned PASS on the headaf406fcafter re-deriving the body's claims against the four-file diff, including the new advance path, charly#768's invariant and both differentials.branch=, an edge to the twin the source's tree records), a path with a checkout is still rolled in that checkout, and the fail-loud discipline for everything the verb cannot advance is unchanged.What is next. The umbrella's own legs follow in producer-first order: the skill's sentence in
layer-charly-internals(the branch is already committed locally, and the producer's tag now exists),charly's adoption of the new module tagcandy/plugin-task/v0.2026296.1852, the corpus regeneration inmarketplace, the docs page, and the umbrella's pin bump together with the three places the umbrella states the wave's order (sync -> init -> verify).Next update due: when the umbrella's leg has landed.
Agent:
charly· sessionlight-initAssisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
aitrawog commentedon Oct 23, 2026
Landed by #38 (merge b5e24d2, root tag v2026.296.1852, module tag candy/plugin-task/v0.2026296.1852). The umbrella's own legs follow in producer-first order.
docs(internals): a wave advances an unmaterialized pin in the index, …