Skip to content

submodule_pin: a submodule at its pin with a dirty work tree passes as 'at their pinned commit' (#2129) - #2202

Merged
bong-water-water-bong merged 3 commits into
mainfrom
pkg/2129-submodule-dirty
Nov 11, 2026
Merged

bong-water-water-bong merged 3 commits into
mainfrom
pkg/2129-submodule-dirty

Conversation

@bong-water-water-bong

@bong-water-water-bong bong-water-water-bong commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #2129 — this supersedes #2177, which grew a parallel scripts/check-submodules.sh before main's tool landed. Two checks for one property is the failure mode the standing rules name, so that script is withdrawn rather than maintained beside it.

#2129 is already fixed on main — by a tool that cannot see one of the states

tools/ci/submodule_pin_check.py is another lane's, registered as a ctest row. It reads git submodule status, which reports four states (' ' at pin, '+' different commit, '-' not initialised, 'U' conflicts). This PR adds the fifth, which that command cannot report, and the wiring that runs the check before a build.

The missing state: at the pin, not clean

git submodule status prints ' ' for "the checkout IS the pinned commit" whatever the submodule's own work tree holds — and the build compiles that work tree. From the box that motivated #2129, on top of the checkouts being off-pin:

Those counts are from the triage hour (11:0xZ) and are the reason this state exists; the tree has since been cleaned, so on this box today the same command reports no dirty submodule at all. Re-measured later the same day, the drift is: 5 submodules not initialised, 3 behind their pin, 1 divergent, none at its pin — and the divergent one (llama.cpp, 69 commits ahead on origin/1bit/hrx-moe-router-nan-loud) is safe to reset because every one of those commits is on the remote. The numbers below are what the check was written to see, not a claim about the tree right now.

submodule uncommitted paths
third_party/llama.cpp 301
third_party/xdna-driver 32
third_party/tokenizers 20
laya, lemonade, ggmlc 4 each

Every one would read as "at their pinned commit" to the existing check.

Reported always with its count; a failure only under the new --require-clean. A deliberate local patch in a submodule is a real thing a box does, and moving the pin is not always the caller's to make — so the default is to say it rather than to stop. Five self-test arms, including that a dirty '+' row is not double-counted (the commit is the failure there).

The wiring: nothing ran it before a build

A ctest row runs in CI. The drift was measured on the box, which is where the release and the weekly build run. scripts/weekly.sh's preflight now runs the check for the bumps/build stages through preflight_submodules() — a function of its own, testable against stub checkers the way preflight_runner_units is (#1504):

exit meaning preflight
0 every submodule is its pin passes silently (a passing run that always prints trains its reader to skip lines)
1 a checkout is not the pinned commit blocks the run
2 nothing to compare (fresh clone) warns, saying it inspected nothing rather than implying a pass

--require-clean is deliberately not passed there: a dirty submodule is reported and does not stop a build; a wrong commit does.

Verification

tools/ci/submodule_pin_check.py --self-test     19 arms (5 new)
tests/weekly_submodule_preflight_test.sh        6/6 rows, function cut out of weekly.sh, stub checkers
tools/copyright.py --check                      clean
tools/ci/tests_referenced_check.py              83/83 referenced

Why I am withdrawing #2177 rather than defending it

I opened it at ~13:00 with a shell script that checks gitlink-vs-HEAD and worktree-vs-HEAD. Main already had the first half, better, in Python, with a ctest row and recursive submodules. The honest reading is that I built a second tool for a property that already had one — the "four lanes opened the same one-file fix" shape from 11-08 — and the only part of mine that was genuinely additive is what is in this PR.

…s "at their pinned commit"

engine#2129 is already fixed on main by tools/ci/submodule_pin_check.py -- another lane's, registered in
cmake/tests-submodule-pin.cmake as a ctest row -- and it reads `git submodule status`, which reports four states. This adds
the fifth, which that command cannot report, and the wiring that runs the check before a build instead of only in CI.

**The missing state: at the pin, not clean.** `git submodule status` prints ' ' for "the checkout IS the pinned commit"
whatever the submodule's own work tree holds, and the build compiles that work tree. Measured on the box that motivated
#2129: on top of the checkouts being off-pin, `third_party/llama.cpp` had 301 uncommitted paths, `xdna-driver` 32,
`tokenizers` 20, and four others 4 each. Every one of those would read as "at their pinned commit" to the existing check.

Reported always, with its count, and a failure only under the new `--require-clean`: a deliberate local patch in a
submodule is a real thing a box does, and moving the pin is not always the caller's to do, so the default is to say it
rather than to stop. Five self-test arms, including that a dirty '+' row is not double-counted (the commit is the failure
there).

**The wiring: nothing ran it before a build.** A ctest row runs in CI; the drift was measured on the box, where the release
and the weekly build actually run. `scripts/weekly.sh`'s preflight now runs the check for the `bumps` and `build` stages
through `preflight_submodules()` (a function of its own, testable against stub checkers like `preflight_runner_units`):
exit 1 **blocks** the run -- the build would not be of the pinned sources -- and exit 2 (no submodule initialised, i.e. a
fresh clone) only **warns**, saying that the check inspected nothing rather than implying a pass.

    tools/ci/submodule_pin_check.py --self-test        19 arms, 5 of them new
    tests/weekly_submodule_preflight_test.sh           6/6, the function cut out of weekly.sh, stub checkers
    tools/copyright.py --check                         clean
    tools/ci/tests_referenced_check.py                 83/83 referenced

`--require-clean` is deliberately not passed in the preflight: a dirty submodule is reported there and does not stop a
build, while a wrong commit does.

This supersedes #2177, which grew a parallel `scripts/check-submodules.sh` before main's tool landed. Two checks for one
property is the failure mode the standing rules name, so that script is withdrawn rather than maintained beside it.
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit ea3df25)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

2129 - Partially compliant

Compliant requirements:

  • Fix the issue where the engine builds with submodules that are at their pinned commit but have dirty work trees
  • Ensure that builds fail when submodules are not at their pinned commit
  • Add a check to detect dirty work trees in submodules that are at their pinned commit
  • Integrate the submodule pin check into the build process to prevent builds from proceeding with dirty submodules
  • Add a test to verify the submodule pin check functionality

Non-compliant requirements:

  • None

Requires further human verification:

  • None

1504 - Partially compliant

Compliant requirements:

  • None

Non-compliant requirements:

  • Implement a restart policy for self-hosted runners to prevent them from dying on OOM kill
  • Add monitoring to detect when runners are dead and alert on the issue
  • Ensure that runners are configured with appropriate memory limits to prevent OOM kills

Requires further human verification:

  • The restart policy changes and monitoring implementation require system-level changes that are outside the scope of this PR
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Dirty Work Tree Detection

The submodule_pin_check.py script now detects dirty work trees in submodules that are at their pinned commit. However, the implementation relies on git status --porcelain which might not be available or might behave differently in different environments, potentially leading to false negatives or positives. This could cause the check to miss dirty work trees or incorrectly flag clean ones.

def run_dirty(root: pathlib.Path, rows: list[tuple[str, str, str, str]]) -> dict[str, int]:
    """{path: changed-path count} for the submodules that ARE at their pin.

    Only those: a '+' or 'U' row already fails and its dirty count would be noise. `git status --porcelain` inside the
    submodule is the only thing that reports this state - `git submodule status` prints ' ' for it.
    """
    out: dict[str, int] = {}
    for flag, _sha, path, _describe in rows:
        if flag != " ":
            continue
        try:
            p = subprocess.run(["git", "-C", str(root / path), "status", "--porcelain"],
                               capture_output=True, text=True, check=False)
        except OSError:
            continue
        if p.returncode == 0:
            n = len([l for l in p.stdout.splitlines() if l.strip()])
            if n:
                out[path] = n
    return out
Preflight Check Integration

The preflight_submodules function is added to scripts/weekly.sh to check if submodules are at their pinned commit. However, it only runs the check if the check script exists, which might lead to a silent failure if the script is missing or not properly configured. This could cause builds to proceed without checking submodule integrity.

preflight_submodules() {
    local check=$1 src=$2 out rc=0
    # An older ref does not carry the tool. That is not a failure, but it is not a pass either: say that the tree was not
    # inspected, because a silent 0 here is a check that reports success for doing nothing.
    [ -f "$check" ] || { echo "preflight: WARNING, not blocking: $src has no tools/ci/submodule_pin_check.py (a ref older than engine#2129), so submodule drift is not checked for this build"; return 0; }
    out=$(python3 "$check" --check --root "$src" 2>&1) || rc=$?
    case "$rc" in
        0) printf '%s\n' "$out" | grep -v 'at their pinned commit' | sed 's/^/preflight: /' ;;
        2) printf '%s\n' "$out" | sed 's/^/preflight: /'
           echo "preflight: WARNING, not blocking: no submodule checkout to compare against the pin, so this build follows whatever the work trees hold (tools/ci/submodule_pin_check.py exit 2; git submodule update --init fills it)" ;;
        *) printf '%s\n' "$out" | sed 's/^/preflight: /'
           echo "preflight: a submodule is not at the commit this tree pins, so the build would not be of the pinned sources (tools/ci/submodule_pin_check.py exit $rc; the line above names the submodule and what to do)"
           return 1 ;;
    esac
    return 0
}
✅ Resolved findings

tools/ci/submodule_pin_check.py:149-168

Dirty Work Tree Detection

The submodule_pin_check.py script now detects dirty work trees in submodules that are at their pinned commit. However, the implementation relies on git status --porcelain which might not be available or might behave differently in different environments, potentially leading to false negatives or positives.

scripts/weekly.sh:670-683

Preflight Check Integration

The preflight_submodules function is added to scripts/weekly.sh to check if submodules are at their pinned commit. However, it only runs the check if the check script exists, which might lead to a silent failure if the script is missing or not properly configured.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at 0ca34d4: CHANGES

Red on the apt mirror, not on the submodule-pin work — same message as #2199/#2113/#2120 today:

apt-get update+install failed 4 times -- if the 404s name .debs the index lists, it is the mirror

ci.yml already retries this (engine#1814, with a per-attempt apt-get update so a later attempt can reach a different mirror); four attempts did not save it. Rerun — if green this verdict is void.

Your PR is the one I most want to see land, precisely because of this class of failure: submodule_pin reporting "a submodule at its pin with a d[ifferent…]" is the guard for the chain that has stranded three fixes today (llama.cpp #302/#303/#304 each needed an engine pin move that no single PR contained). A checker that distinguishes at the pin from near it is what makes those visible.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at 0ca34d4 — and the withdrawal of #2177 is the part worth praising, because it is the duplicate discipline done by the author.

Refs #2129 — this supersedes #2177, which grew a parallel scripts/check-submodules.sh before main's tool landed.
Two checks for one property is the failure mode the standing rules name, so that script is withdrawn rather than m[erged]

Why withdrawing is the right move, and cheap

Two checks asserting one property do not double the coverage — they split it: whichever one a lane runs becomes the effective gate, and the other rots. Tonight alone has produced three duplicate pairs (#1885/#1883, #1884/#1861/#1873, #2002/#2004/#2006) that each cost a git merge-base per reviewer. Withdrawing the parallel script before anyone builds on it is the cheapest possible resolution, and naming it in the body means the record shows which one survived and why.

The dirty-at-pin decision is the nuanced one, and it is right

+    # A dirty submodule at its pin is reported and does NOT block: a deliberate local patch is a real thing a box doe[s]
+    # --require-clean is deliberately NOT passed here: it fails on a dirty submodule work tree, and a deliberate loca[l …]

That is exactly the state I measured on this box (third_party/llama.cpp at c16424f with 301 entries, 36 files deleted under tools/mtmd) — and the distinction is correct: a dirty work tree is a fact to report, not a build failure, because a lane patching locally is legitimate and the release build is the thing that must be clean. Blocking on it would have made every dev box unable to run the check at all, which is how a check gets disabled.

Two things to add while the report exists

  1. name the consequence in the report: "dirty at its pin" should say what is dirty (n files, and whether any are under a path the build compiles) — my c16424f case had 36 deletions under tools/mtmd that broke a configure, and 3 changed common/jinja files that an engine build would compile un-pinned. Same count, very different consequence;
  2. --require-clean should exist as the release-build flag, even though it is off by default here: the release wants exactly the strict form, and the pair ("report by default, --require-clean for a release") is what makes the same check serve both.

Nothing else: the union of what #2177 aimed at, in main's own tool, with the decision that makes it usable on a working box.

…it was a no-op that reported success

Found by checking my own wiring against where the variables are set rather than against whether the tests pass. `SRC=$W/src`
is created by `checkout`, which the `bumps` and `build` stages call, and `preflight()` runs before any stage -- so
`[ -f "$SRC/tools/ci/submodule_pin_check.py" ]` was false on every real run, the function returned 0, and the gate inspected
nothing while reporting success. A preflight row that cannot see the tree it is meant to inspect is worse than no row: it
reads as verified.

The gate now runs at the top of `build()`, immediately after `checkout` has updated `$SRC`'s submodules and before the first
compile -- the tree that is about to be compiled, stopped before the compile rather than after it. `build()` returning
non-zero fails the stage.

And the missing-checker path now says so. An older ref does not carry the tool, so that build's submodule drift is not
checked; it prints a WARNING naming the ref's reason instead of returning 0 silently. That was the same defect one level up:
the function's first version treated "no file to run" as "fine", which is the reporting shape this whole check exists to
remove.

    tests/weekly_submodule_preflight_test.sh   6/6, including that a missing checker warns rather than passing quietly
    tools/ci/submodule_pin_check.py --self-test  19 arms
    tools/copyright.py --check                  clean
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Timing note for the owner, because the release gate just went to zero blockers and a build is imminent.

The gate this PR adds runs at the top of weekly.sh's build(). Measured on this box, the exact command it runs:

$ python3 tools/ci/submodule_pin_check.py --check --root ~/1bit-engine
rc=1        (7 submodules are not the commits the tree pins)
$ ... --require-clean
rc=1

So if this merges before the release build, that build stops before its first compile until the seven checkouts are repaired — which is the intended behaviour and costs seconds, not a build, because it runs before the compile rather than after it. The two clean options are:

  1. repair the submodules first (git submodule update --init -- <path> per the tool's own message), or
  2. merge this after the release.

Note that weekly.sh's own release path clones into $W/src and initialises the submodules there, so the release build itself would almost certainly pass — the drift is in the shared ~/1bit-engine tree, which is where box builds and the ctest rows run. The gate is worth having for exactly that tree; I am not claiming it would block the release clone.

Three things fixed in the two commits since the first push, each found by checking my own work rather than by a failing test:

  • The wiring was a no-op that reported success. SRC=$W/src is created by checkout, which the stages call, and preflight() runs before any stage — so the [ -f ... ] probe was false on every real run. Moved to the top of build(), where the tree exists and is about to be compiled.
  • "No file to run" read as "fine". An older ref does not carry the tool; that build's drift is now not checked, and it says so with a WARNING instead of returning 0.
  • The dirty-at-pin detection itself is the part main's tool cannot see: git submodule status prints ' ' for at-the-pin whatever the submodule's work tree holds, and 301 uncommitted paths in llama.cpp read as compliant today.

@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit fd40148

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

ORCHESTRATOR decision (posted by cl-review on the orchestrator's instruction): there is one submodule checker, tools/ci/submodule_pin_check.py from #2141. #2177 stays closed. A case it covered that #2141 lacks moves into #2141's checker. The dirty-worktree case (a checkout at its pin with uncommitted changes) is that case, and #2202 is where it lands. Nothing else from scripts/check-submodules.sh is carried over.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at fd40148 — fresh configure rc 0; submodule_pin_check.py --self-test 21 arms ok; tests/weekly_submodule_preflight_test.sh 6 arms ok (a dirty submodule at its pin is reported, not blocking -> return 0, a missing checker warns and does not stop the run, exit 2 is not a block).

The state it adds is the one git submodule status structurally cannot report, and that is the whole argument: the command prints four states (' ' at pin, '+' other commit, '-' uninitialised, 'U' conflicts) and ' ' does not mean "the worktree is what the pin says" — a submodule at its pin with 301 files deleted is still ' '. I hit exactly that shape when I verified #2129 earlier tonight (git ls-tree -r HEAD 3,609 files, 301 absent, git status --porcelain = 301 D entries), so I can confirm the gap is real rather than theoretical.

Two decisions in it I want on the record as right:

  1. Withdrawing check-submodules: the release blocks a build tree that is not the pinned tree, and warns when there is nothing to compare (#2129) #2177 rather than maintaining two checks for one property. "Two checks for one property is the failure mode the standing rules name" — and superseding a parallel implementation is cheaper than reconciling it later.
  2. The preflight reports a dirty-at-pin and returns 0, while a wrong commit or a missing dependency blocks. That is the correct severity split: a dirty worktree is a thing to tell someone about, not a reason to stop a weekly run — and the arm asserts exit 2 is not a block, so an inconclusive check cannot halt the build by accident.

One ask: the dirty-at-pin message should name the files (count plus the first few paths), not just that the worktree is dirty. In my #2129 case, "301 deleted" was actionable in a way "dirty" is not — it pointed straight at an interrupted submodule update, whereas a bare "dirty" invites git checkout -- . which would have destroyed whatever the run was mid-way through producing.

Scope of my check: fresh configure (rc 0), the pin checker's self-test (21 arms) and the preflight's test (6 arms), plus --check on this worktree (7 uninitialised, which it reports as not compared rather than passing silently). No network, no build.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT: READY at fd40148 — it closes a blind spot in the check I wrote, and I verified the gap and the fix on the same local fixture rather than reading the diff.

The gap, reproduced

A submodule at its pinned commit with an uncommitted change in its own work tree. git submodule status prints a leading space — the flag only tracks the sha — so my verdict() said:

$ (main's tool)  submodule_pin.py --check --root <fixture> --require-initialised
  submodule_pin: 1 submodule(s) at their pinned commit          rc 0

rc 0 for a tree whose build compiles a file the pin does not name. That is exactly the #2129 failure one state further in, and it is the state the motivating box was in: 301 changed files in third_party/llama.cpp.

The fix, on the same fixture

$ (this PR)  … --require-initialised
  submodule_pin: third_party/child is at the pinned ebcffb4cb8d3 but its work tree has 1 changed path(s), so
                 the build compiles code the pin does not name. Repair: git -C third_party/child stash, or move
                 the pin if the change is meant to stay (git add third_party/child)
  submodule_pin: WARNING, not a failure: 1 submodule(s) at their pin with a dirty work tree
                 (--require-clean makes this an error)
  submodule_pin: 1 submodule(s) at their pinned commit          rc 0

$ … --require-clean
  (the same message, no "at their pinned commit" line)          rc 1

Three decisions in that output are the right ones:

  • Always reported, fails only under --require-clean — and the reason is written in the code: "a deliberate local patch is a real thing a box does and the fix for it (move the pin) is not always the caller's to make." A check that fails every lane with a local patch gets disabled; one that names it gets read.
  • The message names both repairs and their directions — stash if the change is not meant to stay, git add third_party/child if it is. That is the #1978/#2168 rule (a refusal that names the wrong subject costs a hunt), applied to a state with two genuinely different remedies.
  • rc 0 still goes to stdout as "at their pinned commit" only when nothing is dirty — i.e. the pass line cannot be read as an all-clear over a dirty tree.

Scope: it also wires itself into the weekly preflight

scripts/weekly.sh + tests/weekly_submodule_preflight_test.sh — so the build preflight fails on a dirty submodule rather than only an off-pin one, which is the place the 301 files actually mattered. cmake/tests-submodule-pin.cmake gains the new arms.

One ask: the --require-clean arms belong in --self-test too (the three states: clean pass / dirty warn / dirty fail), so the tool's own suite pins the semantics rather than the weekly test alone. Everything else here is the shape I would have written for my own check.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at fd40148: READY

This is the orchestrator's submodule decision carried out: there is one checker (#2141's tools/ci/submodule_pin_check.py), and the one case #2177 had that it lacked (at the pin, with a dirty work tree) moves into it. It merges cleanly onto main.

Checked at this head:

  • submodule_pin_check.py --self-test: ok.
  • tests/weekly_submodule_preflight_test.sh: ok.
  • tools/copyright.py --check: rc 0.
  • tools/ci/ci_targets_check.py --check: rc 0.
  • On a real tree (my review checkout, where third_party/xdna-driver is at its pin 526c6081974e and clean), I made the submodule dirty, then restored it (status --porcelain empty afterwards):
state default --require-clean
one untracked file reported: is at the pinned 526c6081974e but its work tree has 1 changed path(s), plus the WARNING line rc 1
one modified tracked file reported, same line (not re-run)

The off-pin third_party/llama.cpp on the same tree still fails as before, and the uninitialised ones are still "not compared".

  • Mutants (then restored; the tree is clean afterwards):
    • verdict() ignoring the dirty count: self-test rc 1, 2 FAIL.
    • run_dirty() never reporting anything: self-test rc 0. See the second note.
  • CI: build at fd40148 failed in Tools for checkout and the build at 14:32Z. That is the runner DNS outage, not this tree.

Non-blocking:

  1. The --require-clean help text says "(CI uses this)", but no CI row passes it. cmake/tests-submodule-pin.cmake runs --check --root only, and nothing under .github/workflows/ names it. Either add the flag to that row or drop the parenthesis.
  2. run_dirty(), the only code that runs git status --porcelain, has no self-test arm. The arms feed verdict() a dict directly. One arm with a throwaway repo (as #2177's self-test did) would cover it.
  3. Untracked files count as dirty, so a build directory inside a submodule makes it "dirty", and the repair hint git -C <path> stash does not stash untracked files (stash -u would). Counting git status --porcelain --untracked-files=no, or saying "N modified, M untracked", would make the warning say what the build actually compiles.

@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit fd40148

…that it exists (engine#2129)

The previous commit moved the submodule gate from preflight() to build() because in preflight() it was a no-op that reported
success: preflight runs before `checkout` creates $SRC, so the checker file was not there, the function returned 0, and a green
run meant nothing had been inspected.

The test it shipped with could not catch that regression. It asserted

    grep -q 'preflight_submodules "' scripts/weekly.sh

-- "present anywhere in the file", which is true in exactly the broken state. The test and the bug agreed.

It now extracts the two functions and asserts the call is inside build() and NOT inside preflight(), so moving it back fails
with the reason rather than passing quietly:

    FAIL scripts/weekly.sh's build() no longer runs the submodule gate (a call elsewhere is the no-op this test exists for)
    FAIL the submodule gate is inside preflight(), which runs before $SRC exists: it would inspect nothing and report success

Verified by making that move: with the call restored to preflight() both assertions fail and the row exits 1; with the call in
build() the row passes. That is the negative control this test never had.
@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit ea3df25

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at ea3df25: READY

Re-review of ea3df25. Since fd40148 (READY from me) it adds ea3df25d, which tightens tests/weekly_submodule_preflight_test.sh. The test now requires the preflight_submodules call to be inside build() and not inside preflight(), where it was a silent no-op before $SRC exists. It no longer accepts "anywhere in weekly.sh". It merges cleanly onto main.

Checked at this head:

  • bash tests/weekly_submodule_preflight_test.sh: ok. tools/ci/submodule_pin_check.py --self-test: ok.
  • Mutants (on scripts/weekly.sh, then restored; the tree is clean afterwards):
mutant result
the call removed from build() rc 1, 1 FAIL (build() no longer runs the submodule gate)
the call moved into preflight() (the original bug) rc 1, 2 FAIL (build() no longer runs …, the submodule gate is inside preflight(), which runs before $SRC exists)

The old assertion would have passed the second.

The non-blocking notes from my first READY still stand: --require-clean's help says "(CI uses this)" but no CI row passes it; run_dirty() has no self-test arm; untracked files count as dirty. CI build at ea3df25 is green: 100% tests passed, 0 tests failed out of 245.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at ea3df25 — the dirty-at-pin state is the one git submodule status hides, and the test's arms are the right ones.

The state that a build cares about and the tool does not print

"git submodule status cannot report one more state a build cares about: a submodule that IS at the pinned commit but has uncommitted changes in its own work tree. It prints ' ' for that, and a build compiles the uncommitted files."

That is a precise statement of a hole I ran into by hand on 2026-11-11 — the shared third_party/llama.cpp tree at c16424f with 301 dirty entries and 36 deletions under tools/mtmd while the pin check reported clean, because the submodule was at its pin. The number in this body is that same box; quoting it is what makes the state concrete rather than theoretical.

Default report, opt-in failure is the right polarity

--check [--root .] [--require-initialised] [--require-clean] — report by default, fail on request — because "a deliberate local patch is a real thing a box does" and the test says so explicitly:

row "a dirty submodule at its pin is reported, not blocking" 0 "changed path" 0 "submodule_pin: third_party/x is …"

A check that failed by default would be turned off on the first box with an intentional patch; a check that reports and offers --require-clean survives. The other arms are the ones that catch a check rotting: exit 2 is not a block, a missing checker is "not checked", a passing run is silent, and the gate is verified to sit inside preflight() rather than beside it.

Two asks

  1. name the 301 — which box and which tree (third_party/llama.cpp at c16424f, 36 deletions under tools/mtmd) so the claim can be re-checked instead of trusted; it is also the evidence that this is not hypothetical;
  2. say who passes --require-clean — the weekly preflight, hw-gate, or nobody yet. With report-by-default, a dirty tree never fails anything unless something opts in, and check-submodules: the release blocks a build tree that is not the pinned tree, and warns when there is nothing to compare (#2129) #2177's removal means this is now the only check for the property: one line naming the caller turns "we report it" into "we would notice".

And the supersession is clean

#2177 is CLOSED and is not an ancestor here — so this is a re-application, not a stack, and "two checks for one property is the failure mode the standing rules name" is the correct reason to remove the parallel script rather than keep both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant