Files
ersatztv/docs/decisions/records/process/bom-format-detection-recipe.md
T
timothyandtimothy 84165ab755
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 27s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m38s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m25s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m27s
fix(797): the BOM guard was fail-open wherever xxd is not installed (#798)
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-08-14 16:50:59 +00:00

3.1 KiB

key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
key title status since supersedes superseded-by rule signals mechanics
process.bom-format-detection-recipe 2026-07-21 — BOM/format pre-push detection: use the `od` byte check, and run `dotnet format --include` under `bash -c` (#542, detector corrected #797) active 2026-07-21 none none Before any push touching `.cs`, detect BOMs with the `od -A n -t x1 -N 3` byte check and verify the format gate with `dotnet format --include` run under `bash -c`, never bare zsh. NOT `xxd`: it ships with vim and is absent on plain Linux hosts including this repo's CI runner, where the substitution yields empty, never matches, and the check reports all-clean — the same all-clean-detector failure this record was written about, in the detector it prescribed. UTF-8 BOM · `efbbbf` · `dotnet format --verify-no-changes` · `--include` · `mapfile` · zsh vs bash · #311 format gate · detector verification · paths: `ErsatzTV.sln`, `.editorconfig` · issues: #542, #311, #70, PR #402, PR #405 Local pre-push shell; CI's format job recipe (`ci.format-gate-folder-mode`). The BOM *policy* is `release.format-as-you-touch-rebase`; this record is the mechanics only.

Detection loop, verbatim:

for f in $(git diff --name-only origin/main...HEAD -- '*.cs'); do [ "$(od -A n -t x1 -N 3 < "$f" | tr -d ' \n')" = efbbbf ] && echo "BOM: $f"; done

The prescribed detector was itself an all-clean detector, corrected 2026-08-14 (#797). This record was written because a detector that can only say "ok" is worse than none. It then prescribed xxd -p, and xxd ships with vim and is absent on plain Linux hosts, including this repo's CI runner — where the substitution yields the empty string, never matches efbbbf, and the loop prints nothing for a tree full of BOMs. The rule failed its own test, in the recipe it recommends, for three weeks.

Read the original warning precisely, because it is still right and it is not about od: the form that reported all-clean over 19 dirty files was od -An -c | grep '357 273 277' — octal character output, whose spacing and escaping vary. The replacement is od -A n -t x1 -N 3, which emits hexadecimal bytes. That is a different invocation of the same tool, and it was measured across 17 inputs (empty, sub-3-byte, exactly the BOM, BOM+NUL, binary, UTF-16 BOM, unreadable, 20 MB, awkward filenames) on BSD od, GNU od, and inside the actual CI runner image: identical in every cell. tr -d ' \n' is load-bearing — BSD pads to a fixed column width and GNU does not.

Verify your detector — use the form above, or read the bytes directly, and distrust a clean result you didn't prove can go dirty.

dotnet format --include DOES work here — an earlier note claiming it silently no-ops was WRONG. The apparent no-op was the shell: CI's recipe uses mapfile, which is bash-only, and the default shell here is zsh → empty array → zero files → exit 0. Working form:

bash -c 'mapfile -t files < <(git diff --name-only --diff-filter=ACM origin/main...HEAD -- "*.cs")
dotnet format ErsatzTV.sln --no-restore --verify-no-changes --include "${files[@]}"'   # exit 0 = gate passes