docs(multi-repo): gate a template file-sync on a purely additive diff - #161
Conversation
Writing a shared file over each consumer destroys repo-specific content silently. Nothing errors and nothing is reported; the loss surfaces when someone misses what used to be there. The sweep should decide per repo, and a removal should stop it rather than be applied. Two details in the snippet are load-bearing, and both obvious shorter forms are wrong. They are in the text because the first version of this change got them wrong and an adversarial pass caught it: `grep -cE '^-[^-]'` looks correct — it skips the `--- file` header — but it also skips every removed BLANK line and every removed line whose own first character is `-`, which is what a YAML list item at column 0 looks like. Both then count as zero removals and the repo lands in the FIRST branch: reported as already current, never flagged, never opened. Measured: cur='on:\n- push\n- local\n' against tpl='on:\n- push\n' scores +0/-0 under the short form and +0/-1 under `tail -n +3 | grep -c '^-'`. And `diff`'s exit status has to be read before the counts are trusted. With the file absent from the consumer, diff exits 2 with empty stdout, both counts are 0, and `|| true` swallows it — so the repositories MISSING the shared file are exactly the ones reported as synced. `|| true` is still needed on `grep -c` (no matches exits 1, fatal under set -e); it just must not be the only status handling. The section also carries the two things the gate must not skip: read `intentional-drift:` as a key rather than grepping the manifest, since those manifests mention managed files in prose and one such comment reads "byte-identical, drift-enforced checks.yml" while the key lists other files entirely; and distrust an implausible classification rate, because a dry run claiming most of a fleet has opted out of one shared file is a broken query. The 21-repository observation is scoped to what was actually measured: that sweep ran the short pattern, so "the other twenty removed nothing" holds only for removals that pattern can see. Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_0144iD1P22LotW8rxmxrNGro Agent-Host: 0493f0 Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
The gate uses an early `return`, which needs a function. Shown as a bare block it breaks when pasted into a loop, where `continue` is the right word. Wrapped as `sync_one()` with the two inputs named, and the alternative stated. Verified by running the published snippet verbatim against five fixtures: removed blank lines and a removed YAML list item both reach MANUAL (+0/-2 and +0/-1), a purely additive diff reaches the apply branch, identical files report already current, and an absent consumer file reports CANNOT COMPARE. Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_0144iD1P22LotW8rxmxrNGro Agent-Host: 0493f0 Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
|
|
The bot review was unavailable: Copilot is out of quota, account-wide and monthly. The one entry on this PR sits on an older commit and is an error body; there is no approval on The snippet was executed verbatim as published, wrapped exactly as the file now shows it, against five fixtures: The first two are the cases the original version of this snippet got wrong: The Scope stated rather than glossed: the 21-repository sweep cited in the section ran the short pattern, so "the other twenty removed nothing" holds only for removals that pattern can see. The earlier draft presented that hit rate as evidence the gate worked; it isn't, and an editorializing sentence to that effect was removed. Verified independently: Repo gates: Assisted by claude-code:claude-opus-5 — Session |
|
Self-review: 4b8ab52 The review this pull request demands is unsatisfiable (Copilot quota wall or repeated bot failures on this head). Per the documented fallback, the diff on this head was reviewed by the PR author; this comment is the on-the-record attestation the merge gate reads back. It stops matching on the next push. |



Writing a shared template file over each consumer destroys repo-specific content silently. Nothing errors, nothing is reported, and the loss surfaces later when someone misses what used to be there. This adds a per-repo gate to the dry-run rule: apply where the diff only adds, report the rest for a human.
The two details that are load-bearing
Both are in the text because the first version of this change got them wrong and an adversarial pass caught it before the PR opened.
Strip the header; do not exclude a second character.
grep -cE '^-[^-]'looks right — it skips the--- fileheader — but it also skips every removed blank line and every removed line whose own first character is-. That is exactly what a YAML list item at column 0 looks like:Both counts landing at zero puts the repo in the first branch, so it is reported as already synced — the gate failing in precisely the case it exists for.
Read
diff's exit status before trusting the counts. With the file absent from the consumer,diffexits 2 with empty stdout, both counts are 0, and|| trueswallows the failure. The repositories missing the shared file are then the ones reported as synced.|| trueis still required on thegrep -c— no matches exits 1, fatal underset -e, and underset -eo pipefaileven the ordinary differing-files case aborts becausediffreturns 1 — it simply must not be the only status handling.Two things the gate must not skip
Declared exemptions are read as a key, not grepped:
intentional-drift:lives in.github/template.yaml, and those manifests also mention managed files in prose. One such comment reads "byte-identical, drift-enforced checks.yml" while the key beside it lists other files entirely — a grep for the filename matches the comment and returns the opposite of the truth.And an implausible classification rate is a broken query. A dry run reporting that most of a fleet has deliberately opted out of one shared file is not a state of the world; read one matched item in full before believing the aggregate.
Scope of the observation
The 21-repository sweep cited in the section ran the short pattern, so "the other twenty removed nothing" is established only for removals that pattern can see. The text says so rather than presenting the hit rate as evidence the gate works.
Assisted by claude-code:claude-opus-5 — Session