Repository navigation
fix(tools): patch.sh 2b-ter greps text its patch adds; a gate holds every guard to its patch - #279
Merged
Merged
Conversation
…very guard to its patch patch.sh guards each framework override with `grep -q "<sentinel>"`, so that a second run skips what is applied. Step 2b-ter (#274, merged 2026-10-15) greps "SIMUT override — drop a non-TLS client"; wifiserversecure_drop_plaintext.patch adds "SIMUT override — tell a non-TLS client from a TLS one by its FIRST BYTE". The first run applies the patch, so nothing failed. On a core that already carries it, as the maintainer's has since 2026-10-15, the grep finds nothing and `patch -p1` runs on a patched file. Reproduced on isolated cores (cp -al copies of ~/.platformio, 2026-10-16), patch.sh of 2d6650b: - the maintainer's core, once, no tty: "Reversed (or previously applied) patch detected! Assume -R? [n]", "Apply anyway? [n]", "Skipping patch.", "1 out of 1 hunk ignored -- saving rejects to file libraries/WiFi/src/WiFiServerSecureBearSSL.cpp.rej", exit 1. `set -e` ends the script on that line: 1 of 17 steps said "nada a fazer", no "DONE", and steps 2b-bis to 2n and the cache invalidation of step 3 did not run. A dry run with `patch` stubbed out calls it from one step of 17 (2b-ter), so the other sixteen guards match their patches. - 2b-ter undone with `patch -R`, then patch.sh twice: run 1 applies it (the file's sha256 is the maintainer's), run 2 stops as above. - on a tty the same, after waiting at both prompts; "y" at the first reverses the override (the marker count goes 1 to 0) and the script goes on, exit 0. ~/.platformio was not written: the sha256 of its framework's (inode, size, mtime, path) list and of the two state files, and the target's inode and sha256, are equal before and after. Fix: the sentinel is the head of the line the patch adds. The same runs on fresh cores: the maintainer's core once, exit 0, 17 of 17 "nada a fazer", nothing applied; 2b-ter undone, then twice: run 1 applies only 2b-ter (same sha256), run 2 exit 0, 17 of 17 "nada a fazer"; on a tty, no prompt. tools/check_patch_sentinels.py reads patch.sh against patches/ and holds each `patch` step to four rules: unguarded, unreadable, no-patch, and not-added (the sentinel must be in a `+` line the patch adds to the file the guard greps). On the patch.sh of 2d6650b it reports exactly one finding, 2b-ter at line 138; on this tree, none. tools/test_patch_sentinels.py pins it in 49 checks (the diff reader, synthetic trees, and each of the 17 guards broken four ways on the real tree, one finding each) and runs in the gates job; 22 of 22 one-line changes to the gate are caught by it. The comment above step 2b and the overrides README now say what a sentinel has to be. Ran: ci_gates_local.py --quick, 35 of 35 steps (the native suites, the sanitizer pass and the fuzzer skipped: they build src/, which this does not touch). Not run: a firmware build and the bench. No image changes expected: on a core without the override the old and the new guard both find nothing and apply the same patch (same sha256). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx
angeloINTJ
pushed a commit
that referenced
this pull request
Oct 21, 2026
Takes main from 2d6650b to 90e82d1: #279 (patch.sh's 2b-ter sentinel and its gate, check_patch_sentinels.py, with its test) and #280 (an injected touch ends in loopCore1( ) right after handleTouch( ); the touch sensitivity's Cancelar takes an injection; the calibration's measuring phase ignores it). Conflicts, one line each: - src/DisplayManager_Touch.cpp: the sensitivity Cancelar keeps the branch's uiSensCancelHit( ) and actSensCancel( ) inside main's `p.z >= 50 || simNow` gate, with main's `_lastTouchInjected = simNow;` and its comment before acceptTouch(0); main's simNow hoist, its removal of the later declaration and its `if (simNow) return;` after the verdict buttons (uiCalVerdictHit) merged on their own, as did the expiry in loopCore1( ) (DisplayManager.cpp). - .github/workflows/build.yml: both sides added steps at the same spot; all four kept (the branch's three, then main's patch.sh sentinel step). - tools/flash_budget.json, docs/analysis/PLANO_STABLE.md section 3.1, README.md, README.pt-BR.md, README.es-ES.md: the branch's text, because sizes and counts are measured on the merged tree, not merged as text; the next commit does it. - tools/README.md: the branch's counts and rows plus main's two new rows (check_patch_sentinels.py, test_patch_sentinels.py); counts checked in the next commit. Nothing was built or run on a board for this commit; the host proof follows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 2b-ter of
patch.shdoes not recognise its own patch, so the maintainer's next run stops there. This fixes the sentinel and adds a gate that holds every guard to its patch.The defect
patch.shguards each framework override withgrep -q "<sentinel>", so that a second run skips what is applied. Step 2b-ter (#274, merged 2026-10-15) grepsSIMUT override — drop a non-TLS client.wifiserversecure_drop_plaintext.patchaddsSIMUT override — tell a non-TLS client from a TLS one by its FIRST BYTE. The first run applies the patch, so nothing failed. On a core that already carries it, as the maintainer's has since 2026-10-15, the grep finds nothing andpatch -p1runs on a patched file.Reproduction (2026-10-16,
patch.shof2d6650b)On isolated cores:
cp -al ~/.platformio, the two state files andlwipopts.hcopied out of the hard links first,PLATFORMIO_CORE_DIRset.patch.shwas never run on~/.platformio, and it was not written: the sha256 of its framework's (inode, size, mtime, path) list and of the two state files are equal before and after, and the target file keeps its inode and sha256 (patchwrites a new inode in the copy, the original stays).DONEReversed (or previously applied) patch detected! Assume -R? [n],Apply anyway? [n],Skipping patch.,1 out of 1 hunk ignored -- saving rejects to file libraries/WiFi/src/WiFiServerSecureBearSSL.cpp.rejpatch -R, run 1yat the firstIn words:
patchprompts, rejects the hunk to a.rejfile next to the target (the target is untouched) and exits 1, andset -eendspatch.shon that line. Steps 2b-bis to 2n did not run, nor did step 3, the cache invalidation that makes the nextpio runrebuild after an override merges. An override step added after 2b-ter would not have been applied by the maintainer's next run, and exit status 1 would be the only sign. On a tty,yatAssume -R?takes the override out of the core and the script finishes with exit 0.A dry run on the maintainer's core with
patchstubbed out lists the steps that would call it: one, 2b-ter. The other sixteen sentinels match their patches.The fix
One string in
tools/arduino_pico_overrides/patch.sh: the sentinel is nowSIMUT override — tell a non-TLS client from a TLS one by its FIRST BYTE, the head of the line the patch adds. A comment above step 2b andarduino_pico_overrides/README.md(where it sayspatch.shis idempotent) now say what a sentinel has to be.The same runs on fresh isolated cores:
DONE;Every step's skip message ends "— nada a fazer"; the words "já aplicada" are 2b-ter's own.
The gate
tools/check_patch_sentinels.pyreadspatch.shagainstpatches/, with no framework on disk, so it runs in thegatesjob. Eachpatchstep is held to four rules:unguarded: apatchcommand that is not the else-branch ofif grep -q "<sentinel>" "<file>"; then. Everypatchcommand is read, so a step in a new shape is a finding, not a pass.unreadable: a sentinel with$, a backtick,\,[,],*or^(bash or grep would not read it as written), or a path from a variable the gate cannot resolve.no-patch: the patch file is not there.not-added: the sentinel is not in a+line the patch adds to the file the guard greps. Context lines, removed lines and other files of the same patch do not count (2f grepsHTTPServer.h, 2m and 2n grepHTTPServer.cpp, in patches that touch both).Not read, because they do the same thing every time: the backup of
lwipopts.h(guarded by[ ! -f ... ]) and the copy oflwipopts.h(a plaincp). There is nosedstep.Negative control: on the
patch.shof2d6650bthe gate reports exactly one finding, 2b-ter at line 138, with the line the patch adds; on this tree, none (17 guarded steps).tools/test_patch_sentinels.py(49 checks) pins it: the diff reader, synthetic trees for each rule and for the variablespatch.shbuilds its paths from, the command's exit codes, and on the real tree each of the 17 guards broken four ways (sentinel changed, marker in the patch changed, guard taken away, patch file gone), each exactly one finding at that step, plus 2b-ter with its old sentinel. 22 of 22 one-line changes to the gate are caught by it (3 by the test crashing on a lookup the removed check was guarding, not by a failed check).What was run
python3 tools/test_patch_sentinels.py: 49 of 49.python3 tools/check_patch_sentinels.py: 0 findings in 17 guarded steps.ci_gates_local.py --quickon this branch: 35 of 35 steps ofgatesandstatic-analysis. The native suites, the sanitizer pass and the fuzzer are skipped: they buildsrc/, which this does not touch.CHANGELOG.mdis not touched: the last 40 commits that touchedtools/changed it once, for a firmware change.CI exposure
The firmware jobs run
patch.shafter restoring~/.platformiofrom a cache (build.yml: "the script is idempotent, so a warm cache that already carries them is fine"; keypio-fw-<env>-<hash of platformio.ini>, with prefix fallbacks).platformio.inilast changed on 2026-10-13, before 2b-ter merged, and the ten latestbuild.ymlruns, through #278, are green. My reading, not checked in a log (gh run view --job --logreturned an empty log): CI still restores entries saved before 2b-ter, where the guard is never asked about an applied 2b-ter. The first entry saved after it (a change toplatformio.ini, or an evicted entry recreated) would carry the override, and the next run to restore it would stop at 2b-ter in "Apply framework overrides" in every firmware job.🤖 Generated with Claude Code
https://claude.ai/code/session_01LyLm7Q1o7kCHngE63BnoTx