From dabd07f74e830e9ce4cc62ee83c6f06c7b65f4cb Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 10 Sep 2026 10:06:23 -0400 Subject: [PATCH 1/3] fix(runbook): the copy-this build command was producing the 4MB image it warns against The three sdkconfig.defaults files disagree about flash geometry, and in a single concatenated file the LAST assignment wins: sdkconfig.defaults FLASHSIZE "8MB", partitions_display.csv sdkconfig.defaults.16mb FLASHSIZE "16MB", partitions_16mb.csv, ROLLBACK=y sdkconfig.defaults.esp32c6 FLASHSIZE "4MB", partitions_4mb.csv The command concatenated `.16mb` then `.esp32c6`, so `.esp32c6` overrode `.16mb` and every build came out 4MB with partitions_4mb.csv. Swapping the last two fixes it: `.16mb` is the override layer describing the fleet's real flash geometry, so it must come last. MEASURED both ways, same worktree and container, only the order changed: .16mb then .esp32c6 -> "4MB" partitions_4mb.csv ROLLBACK=y .esp32c6 then .16mb -> "16MB" partitions_16mb.csv ROLLBACK=y Why it survived this long: the only symptom the old text told you to watch for was a missing CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE, and that flag was the one thing the broken order got RIGHT -- `.esp32c6` does not set it, so it came through from `.16mb` either way. The build reported success, the rollback warning did not fire, and the flash size was wrong. Found only by running the runbook's own verification grep against the artifact instead of trusting the build's exit code. Also records an open question rather than papering over it: even corrected, the build yields CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=64, while the fleet is documented at 128 -- and no combination of these three files produces 128. Read it off a running node before editing any defaults file. Co-Authored-By: claude-flow Claude-Session: https://claude.ai/code/session_01PVWMiHQifoYXL7uL3bphrZ --- firmware/esp32-csi-node/RUNBOOK.md | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/firmware/esp32-csi-node/RUNBOOK.md b/firmware/esp32-csi-node/RUNBOOK.md index 6b924e74e3..8d7d878b33 100644 --- a/firmware/esp32-csi-node/RUNBOOK.md +++ b/firmware/esp32-csi-node/RUNBOOK.md @@ -16,12 +16,52 @@ below was learned by getting it wrong at least once. MSYS_NO_PATHCONV=1 docker run --rm \ -v "$(pwd)/firmware/esp32-csi-node:/project" -w /project \ espressif/idf:v5.4 bash -c \ - "cat sdkconfig.defaults sdkconfig.defaults.16mb sdkconfig.defaults.esp32c6 \ + "cat sdkconfig.defaults sdkconfig.defaults.esp32c6 sdkconfig.defaults.16mb \ > sdkconfig.defaults.build && \ SDKCONFIG_DEFAULTS='sdkconfig.defaults.build' idf.py set-target esp32c6 && \ idf.py build" ``` +### CORRECTED 2026-09-10 — the order of the last two files was wrong + +This command previously read `... sdkconfig.defaults.16mb sdkconfig.defaults.esp32c6`, +and **it produced the 4MB image this runbook exists to prevent.** In a single +concatenated file the *last* assignment wins, and the three files disagree: + +| file | sets | +|---|---| +| `sdkconfig.defaults` | `FLASHSIZE "8MB"`, `partitions_display.csv` | +| `sdkconfig.defaults.16mb` | `FLASHSIZE "16MB"`, `partitions_16mb.csv`, `BOOTLOADER_APP_ROLLBACK_ENABLE=y` | +| `sdkconfig.defaults.esp32c6` | `FLASHSIZE "4MB"`, `partitions_4mb.csv` | + +With `.esp32c6` last it overrode `.16mb`, so the build came out 4MB with +`partitions_4mb.csv`. Only the rollback flag survived, because `.esp32c6` does +not set it -- which is exactly why this was hard to spot: the one symptom the +old text warned about (missing rollback) was the one symptom that *didn't* +appear. + +**MEASURED both ways, 2026-09-10**, same worktree, same container, only the +order changed: + +``` +.16mb then .esp32c6 -> FLASHSIZE "4MB" partitions_4mb.csv ROLLBACK=y +.esp32c6 then .16mb -> FLASHSIZE "16MB" partitions_16mb.csv ROLLBACK=y +``` + +`.16mb` must come **last** because it is the override layer: it is the only +file that describes the fleet's actual flash geometry, and every earlier file +is a more general default. + +### OPEN: `DYNAMIC_TX_BUFFER_NUM` is 64 here, but the fleet is documented at 128 + +Even with the corrected order the build yields +`CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=64`. Only `sdkconfig.defaults` sets it +(to 64) and neither other file overrides it, so **no combination of these three +files produces 128.** Either 128 arrives from somewhere not yet found, or the +"fleet runs 128" claim is wrong. Do not "fix" this by editing a defaults file +until that is settled -- read it off a running node first. The verification +grep below therefore expects 64 today, not 128. + Takes ~3 minutes cold, well under a minute incremental. ### The three defaults files are NOT all automatic From a02325c06bad70ab345ea87f47eec43cea208b42 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 10 Sep 2026 10:54:06 -0400 Subject: [PATCH 2/3] docs(runbook): the corrected build order was already documented in the file itself sdkconfig.defaults.16mb's own header comment says: idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32c6;sdkconfig.defaults.16mb" build which is the swapped order. The runbook's `cat` had the last two arguments transposed relative to the instructions sitting inside the very file it was concatenating. So the fix in 5835cb4b is not a hypothesis that happened to measure well -- it restores documented intent. Two independent confirmations now stand behind it: the A/B build (order the only variable, 4MB vs 16MB) and this docstring. Co-Authored-By: claude-flow Claude-Session: https://claude.ai/code/session_01PVWMiHQifoYXL7uL3bphrZ --- firmware/esp32-csi-node/RUNBOOK.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/firmware/esp32-csi-node/RUNBOOK.md b/firmware/esp32-csi-node/RUNBOOK.md index 8d7d878b33..72b067b7a1 100644 --- a/firmware/esp32-csi-node/RUNBOOK.md +++ b/firmware/esp32-csi-node/RUNBOOK.md @@ -52,6 +52,19 @@ order changed: file that describes the fleet's actual flash geometry, and every earlier file is a more general default. +**This is not a new theory -- it restores what the file always said.** +`sdkconfig.defaults.16mb` documents the correct order in its own header: + +``` +# Build: +# idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32c6;sdkconfig.defaults.16mb" build +``` + +So there are two independent confirmations: the A/B measurement above, and the +file's own docstring. The runbook's `cat` simply had the last two arguments +transposed relative to the instructions sitting inside the file it was +concatenating. + ### OPEN: `DYNAMIC_TX_BUFFER_NUM` is 64 here, but the fleet is documented at 128 Even with the corrected order the build yields From a9bcbec713060b136665353045f0da875d7fdbfb Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 13 Sep 2026 22:01:02 -0400 Subject: [PATCH 3/3] docs(runbook): give 4MB and 8MB boards their own build command The build section had one command in it and that command was the 16MB fleet's. Anyone on a stock 4MB or 8MB C6 dev board had to infer their own path from a correction notice written for a different flash size, and the surrounding text framed a 4MB image as the failure mode rather than as a supported target. Now there are two commands. The 4MB/8MB one omits sdkconfig.defaults.16mb, so the target overlay is the last word and no override ordering is needed at all. That yields partitions_4mb.csv: two 1.875MB OTA slots against a ~978KB binary. Also records the one real difference, which was not written down anywhere: CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is set only in sdkconfig.defaults.16mb, so a 4MB build has no automatic rollback and an OTA'd image will not be reverted by the bootloader. Nothing about 4MB flash forces that -- partitions_4mb.csv has otadata and two OTA slots -- it is only which layer the flag lives in. Documented rather than changed, because changing it needs a hardware test on a 4MB board. Co-Authored-By: claude-flow --- firmware/esp32-csi-node/RUNBOOK.md | 41 +++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/firmware/esp32-csi-node/RUNBOOK.md b/firmware/esp32-csi-node/RUNBOOK.md index 72b067b7a1..760c76c93e 100644 --- a/firmware/esp32-csi-node/RUNBOOK.md +++ b/firmware/esp32-csi-node/RUNBOOK.md @@ -10,7 +10,10 @@ below was learned by getting it wrong at least once. ## 1. Build -**One command. Copy it.** From the **repository root**, not this directory: +**Pick the command for your board's flash size, then copy it.** Run from the +**repository root**, not this directory. + +### 16MB boards ```bash MSYS_NO_PATHCONV=1 docker run --rm \ @@ -22,10 +25,40 @@ MSYS_NO_PATHCONV=1 docker run --rm \ idf.py build" ``` -### CORRECTED 2026-09-10 — the order of the last two files was wrong +### 4MB and 8MB C6 dev boards + +**Omit `sdkconfig.defaults.16mb` entirely.** `sdkconfig.defaults.esp32c6` +already carries 4MB geometry, so with the 16MB layer absent the target overlay +is the last word and no override is needed: + +```bash +MSYS_NO_PATHCONV=1 docker run --rm \ + -v "$(pwd)/firmware/esp32-csi-node:/project" -w /project \ + espressif/idf:v5.4 bash -c \ + "cat sdkconfig.defaults sdkconfig.defaults.esp32c6 \ + > sdkconfig.defaults.build && \ + SDKCONFIG_DEFAULTS='sdkconfig.defaults.build' idf.py set-target esp32c6 && \ + idf.py build" +``` + +That yields `FLASHSIZE "4MB"` with `partitions_4mb.csv` -- two 1.875 MB OTA +slots against a roughly 978 KB binary, so OTA has ample headroom. + +**One behavioural difference to know about.** `CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE` +is set *only* in `sdkconfig.defaults.16mb`. A 4MB build therefore has no +automatic rollback: an OTA'd image does not boot PENDING_VERIFY and the +bootloader will not revert a bad image on the next boot. Nothing about 4MB +flash forces that -- `partitions_4mb.csv` has an `otadata` partition and two +OTA slots -- it is simply which layer the flag currently lives in. Treat a 4MB +OTA as unguarded until that is changed deliberately, and keep a serial recovery +path available. + +### CORRECTED 2026-09-10 — why order matters on the 16MB path -This command previously read `... sdkconfig.defaults.16mb sdkconfig.defaults.esp32c6`, -and **it produced the 4MB image this runbook exists to prevent.** In a single +The 16MB command previously read +`... sdkconfig.defaults.16mb sdkconfig.defaults.esp32c6`, and **it silently produced a +4MB image on 16MB hardware** -- not a broken build, just the wrong one, which is +why it survived so long. In a single concatenated file the *last* assignment wins, and the three files disagree: | file | sets |