`sed -i 'edit' file` means "edit in place" on every machine
BehavioralExample-Based TestsPre-Promotion Invariant Gates
https://medlab.sublayer.space/c/sed-in-place-is-not-portable/
now fails loudly `sed -i` with no backup suffix is GNU-only; the deck test now uses the portable `sed -i.bak` + `rm -f` form, which edits in place identically on GNU and BSD sed
What broke
The station's status page builds a manifest of "decks" — small documents, each declaring a status — and a validator refuses any corpus that contains an unknown status, among other checks. The test for that validator proves the gate works by corrupting a copy of the corpus and watching the gate refuse it: it rewrites online to a nonsense value and expects the build to reject the result. The rewrite is done with sed, the stream editor, using its in-place-edit flag.
The test was written for the GNU implementation of sed, where sed -i 'edit' file means "edit file in place, keep no backup." On a Mac, the system sed is the BSD implementation, where -i takes a required argument naming the backup file: sed -i 'edit' file is read as "edit in place, name the backup 'edit'," and file — a path on disk — is then treated as the script to run. That fails with sed: invalid command code, and the target file is left untouched.
The test script runs under set -uo pipefail, not set -e, so a failing sed does not stop the script. The corruption it was supposed to perform does not happen, and the script continues as though it had.
How it showed
A scheduled CI run went red with no code regression behind it: no commit had touched the collector or the deck test, yet the suite failed. The raw log line is the signature of the defect: sed: 1: "...": invalid command code f, a BSD sed error meaning it tried to execute a filesystem path as a sed program.
Because the mutation never ran, the deck still carried its valid, original status. The validator accepted it, correctly, and the test reported the gap from the far side of the fault:
gate ACCEPTED a corpus with an unknown status
The "unknown status" was never written. The test's assertion assumed a corrupted copy was being checked; the copy was in fact unmodified, and the gate's acceptance of it was correct. The failure was real, but the reason the test printed was not the actual cause: the tool meant to create the bad input had already failed silently.
What was watching
Example-based tests — the deck test itself — was present and blind. It ran the assertion meant to catch a validator regression, but its corruption step depended on a tool contract, sed -i, that differs across machines. It could manifest the defect only on a BSD sed; running against a GNU one gave no signal that the assumption was wrong.
Pre-promotion invariant gates — the CI run, on its scheduled trigger — fired and reported the red suite. The runner's own startup log recorded Operating System: darwin/arm64, so the fact that the suite was running on a Mac was on screen the whole time; no check compared that against the Linux-only tool contract the suite assumed.
The failure escaped as far as the CI pipeline and no further: the scheduled run that caught it also blocked the deploy. How long the non-portable sed call had been in the test before this is unknown — it dates to the test's first version, and passed for as long as it happened to run on a GNU sed.
Where it sits
This is a behavioral failure: the test's behavior changed from machine to machine with no change to the code, because one of the tools it drove had a different contract on a different platform. The validator it protects did not regress; the test protecting the validator did.
The example-based tests sensor carried a non-portable tool invocation inside the assertion meant to catch a regression, and only a different sed exposed it. The pre-promotion invariant gates sensor fired, but only after the platform mismatch was already live. The missing sensor is a check that the tools a suite invokes match what the suite assumes about them.
What we did
The fix replaces the GNU-only form with a portable one:
sed -i.bak 'edit' file && rm -f file.bak
-i.bak names an explicit backup on both GNU and BSD sed, so "in place" has one meaning on both. rm removes the one artifact either implementation leaves behind. The three affected rewrite sites in the deck test were changed to this form.
The correct pattern already existed in the same repository, in a sibling test (test/10-manual.sh), which had used sed -i.bak ... && rm -f from the start. The deck test was written independently and did not reuse it. The fix brings the deck test in line with the form already in use one file over.
What now fails loudly
- The corruption cannot silently fail to happen. The deck test now uses
sed -i.bak with an explicit rm of the backup, a form that edits in place on both GNU and BSD sed. The mutation runs on either implementation or the script's own assertions catch its absence.
- The two flags are now one idiom. A future test that needs an in-place
edit can copy the form already used by its sibling, rather than writing a new, possibly non-portable one.