Repository navigation
Description
Outcome
RP2040 deployment must never treat a stale Windows CDC history record as a usable runtime port. After a UF2 transfer, fbuild may report a port only when the current scan says it is not known-unhealthy and a bounded openability probe succeeds.
This issue consumes the health-aware records from #1146. Manual recovery must work without #1148; the optional elevated path can be integrated afterward.
Reproduction already observed
On the attached RP2040:
- runtime identity: VID:PID
2E8A:000A; - serial:
5303284720C4641C; - historical runtime port:
COM12; - manual BOOTSEL volume:
RPI-RP2onG:; - UF2 copy succeeded and the ROM volume ejected;
- Windows then exposed
USB\VID_2E8A&PID_000A\5303284720C4641CasCM_PROB_PHANTOM; - opening
COM12exceeded the probe timeout; - fbuild nevertheless returned
COM12as the deploy port.
A remembered serial-number match is not proof that the COM endpoint is live.
Dependency
- fix(serial): preserve Windows PnP health through port probing #1146 must land first.
PicoCdcPortmust consume the enriched fbuild serial record instead of rebuilding a lossy name/serial-only view.
Required RP2040 state machine
Implement the following ordered behavior in crates/fbuild-deploy/src/rp2040.rs and keep pure selection logic in crates/fbuild-deploy/src/rp2040_target.rs.
1. Preflight snapshot
- Catalogue RP2040 runtime CDC candidates by expected board identity/VID:PID, serial, port name, instance ID, and health.
- Keep known-unhealthy candidates for diagnostics, but exclude them from automatic selection and from the 1200-baud touch path.
- An explicit
--port COMxdoes not override knownPhantomorPresentProblemhealth. Fail with the exact health/problem information instead of trying a guaranteed-stale handle. Unknownhealth on hosts without Windows metadata remains eligible only through the existing openability behavior.
2. Runtime-to-BOOTSEL transition
- If exactly one matching healthy runtime CDC endpoint is selected, perform the existing 1200-baud touch.
- Preserve its serial/instance identity as a matching hint, not as a future declaration that its old COM name is usable.
- If no healthy runtime endpoint exists, continue through the existing manual BOOTSEL-volume workflow.
- Never touch an unrelated or ambiguous CDC endpoint.
3. UF2 transfer
- Keep the current BOOTSEL volume identity/ambiguity checks and existing copy/eject diagnostics.
- Multiple matching BOOTSEL volumes remain an error unless the user supplied a selector that makes the target unique.
- A successful UF2 copy proves flash transfer only; it does not prove CDC recovery.
4. Post-flash reacquisition
Use the delayed handoff already added by #1095, but each scan must use #1146 health:
- rescan until the existing bounded deadline;
- select by stable serial/instance identity when available;
- accept a renumbered COM port when the same live device returns;
- require the candidate to be not known-unhealthy;
- require a bounded serial open to succeed;
- only then return it as
DeploymentResult.port.
For a board entered into BOOTSEL manually, a unique pre-flash runtime candidate may be retained only if it was healthy and openable in that same preflight scan. It is still only a hint: after flashing, fbuild must rediscover a healthy/openable endpoint. Do not return the old COM name merely because its serial matches.
5. Recovery result
If UF2 transfer succeeded but CDC did not recover:
- return/record full-flash success separately from port recovery;
- set
DeploymentResult.port = None; - do not emit a stale
FBUILD_DEPLOY_PORTor otherwise advertise the phantom COM name as usable; - show the matching instance ID, health state, problem code, and last open error;
- explain that Windows retained a phantom/bad devnode;
- recommend a direct motherboard USB port and a known data cable;
- give exact manual recovery:
- hold BOOT/BOOTSEL;
- press and release RESET while still holding BOOT;
- keep BOOT held for about two seconds, then release;
- verify an
RPI-RP2volume appears and rerun deploy;
- if feat(windows): add scoped elevated USB PnP recovery helper #1148 is available and the user explicitly selected
--admin, request that one-shot recovery, rescan once through a bounded deadline, and then apply the same health/openability gate.
The deploy message must distinguish ?firmware copied successfully; runtime CDC unavailable? from ?firmware transfer failed.?
Implementation map
crates/fbuild-deploy/src/rp2040.rs- enrich
PicoCdcPort; - update
catalogue_pico_cdc_ports; - gate
touch_1200bps; - make post-flash polling health-aware;
- ensure
post_deploy_recoverydoes not bless the requested/old name after an open failure.
- enrich
crates/fbuild-deploy/src/rp2040_target.rs- keep deterministic candidate matching and ambiguity errors pure/testable;
- never choose a known-unhealthy candidate.
crates/fbuild-daemon/src/handlers/operations/deploy.rs- preserve the distinction between successful flash and absent recovered port;
- do not substitute the pre-deploy/requested port when the deployer returns no healthy port.
- CLI/response rendering
- print the structured recovery diagnostic without converting it back into a generic success port.
Important rejected shortcut
A patch that only remembers ?the unique pre-flash COM port? during manual BOOTSEL is incomplete. It recreates this bug if that record has become CM_PROB_PHANTOM. Any retained target must carry its live health, and the returned post-flash endpoint must be rediscovered and opened.
Deterministic tests
Add injected scan/open fixtures; do not require real USB for unit tests.
Required cases:
- healthy
COM12-> BOOTSEL -> healthy/openableCOM12: accepted; - healthy
COM12-> BOOTSEL -> healthy/openableCOM27with same serial:COM27accepted; - healthy
COM12-> BOOTSEL -> phantomCOM12: flash success, port absent, recovery diagnostic; - present/problem
COM12: excluded before 1200-baud touch; - explicit
--port COM12where COM12 is phantom: rejected with health details; - manual BOOTSEL plus one healthy preflight runtime identity: used only as a hint and revalidated after flash;
- manual BOOTSEL plus only a phantom historical candidate: not used;
- multiple healthy candidates without selector: no guessing;
- delayed healthy CDC appearance within fix(rp2040): wait for delayed Windows CDC handoff #1095 deadline: accepted;
- candidate becomes phantom between scans: never returned;
- open probe fails despite healthy metadata: never returned;
- non-Windows
Unknownhealth plus successful open: preserves existing behavior; - daemon does not fall back to the requested/pre-flash port when result port is
None.
Run:
soldr cargo test -p fbuild-deploy rp2040
soldr cargo test -p fbuild-daemon deploy
soldr cargo clippy -p fbuild-deploy -p fbuild-daemon --all-targets -- -D warnings
Windows hardware validation
Hardware validation is required for this child, but a factory-blank board is not required. Use an existing RP2040 and fbuild only.
To avoid colliding with another checkout's daemon, use a unique five-digit port and dev mode. From the FastLED checkout, with the candidate fbuild worktree installed editable into FastLED's venv:
uv pip install --python .venv\Scripts\python.exe --editable C:\path\to\fbuild-worktree
$env:FBUILD_DEV_MODE = '1'
$env:FBUILD_DAEMON_PORT = '55175'
bash autoresearch rp2040 --rpc-smoke --timeout 120s --skip-lintRecord:
- preflight
fbuild port scanhealth; - selected runtime identity;
- BOOTSEL volume observed;
- UF2 transfer result;
- post-flash port identity and health;
- successful RPC smoke if CDC recovers;
- exact structured diagnostic and absence of a returned port if Windows reproduces the phantom state.
Do not use raw pyserial, picotool, or manual file-copy commands as acceptance evidence.
Acceptance criteria
- Known phantom/problem RP2040 CDC records remain visible diagnostically but are never auto-selected or touched.
- Explicit stale
--portfails with actionable health details. - A healthy CDC endpoint can still be touched into BOOTSEL.
- Renumbered healthy CDC is reacquired by stable identity.
- UF2 success plus CDC failure returns no usable deploy port and does not emit stale
FBUILD_DEPLOY_PORT. - Manual BOOTSEL works without guessing among candidates.
- Recovery instructions identify BOOT as BOOTSEL and give the BOOT+RESET sequence.
- Unit tests cover the transition matrix above.
- A real fbuild-only AutoResearch RPC smoke passes on a healthy attached RP2040, or a reproduced phantom run proves the fail-safe diagnostic path. Full closeout should include the healthy RPC path.
Non-goals
- Do not hide all unusual ports globally; fbuild port scan misses all PJRC/Teensy (VID 16C0) serial ports + no vendor/product resolution for them #962 diagnostic behavior stays intact.
- Do not increase timeouts as the primary fix.
- Do not require a fresh/blank board.
- Do not restart or elevate the daemon.
- Do not implement privileged PnP operations here unless feat(windows): add scoped elevated USB PnP recovery helper #1148 has landed.
Related issues
- meta: Windows USB phantom-device detection and recoverable native-USB deployment #1149 ? parent meta
- fix(serial): preserve Windows PnP health through port probing #1146 ? required health model
- feat(windows): add scoped elevated USB PnP recovery helper #1148 ? optional elevated recovery
- fix(rp2040): wait for delayed Windows CDC handoff #1095 / fix(rp2040): tolerate delayed Windows CDC re-enumeration after UF2 flash #1074 ? existing delayed CDC handoff
- bug(rp2040): reject unrelated pre-existing CDC port after stock-ROM deploy #1080 ? unrelated CDC candidate rejection
- feat(rp2040): zero-touch deployment to an as-received Pico via ROM UF2 #1040 / feat(rp2040): deploy parity with Arduino-Pico, PlatformIO, and official picotool #1049 ? existing RP2040 deployment coverage
Implementation handoff: state-machine ownership and current bug anchor
Begin this child only after #1146 and #1148 are merged to main. Rebase from that main; do not copy a local experimental patch that remembers a pre-flash COM name.
The current dangerous anchor is in crates/fbuild-daemon/src/handlers/operations/deploy.rs: recovery input is chosen as deploy_port_str.or_else(|| deploy_result.port). That prioritizes the requested/pre-flash port even when the deployer has no freshly recovered runtime port. Replace this behavior so post-flash recovery and all response/environment propagation use only a freshly catalogued, health-eligible, bounded-openable endpoint. If CDC recovery fails after a confirmed UF2 transfer, preserve success: true for the transfer but return port: None; never call recovery/open/monitor using the old requested COM name.
Required state-machine seams
Keep candidate selection pure and inject scan/open/clock behavior for tests. Extend the existing RP2040 catalogue and wait_for_cdc_port_with_clock seam rather than adding a second polling path:
- Catalogue
PicoCdcPortwithPortHealth, canonical instance ID, parent instance ID, serial, and name. - Preflight: known-unhealthy candidates are visible in diagnostics but cannot be selected, explicitly touched at 1200 baud, or used as the manual-BOOTSEL hint.
- Transfer: preserve existing BOOTSEL volume ambiguity/identity protections; a successful copy is transfer-only evidence.
- Reacquisition: on every scan, match serial/instance as hints, accept COM renumbering, require health not known-unhealthy, then require bounded open. Only this candidate may become
DeploymentResult.port. - Failure: distinguish transfer success/CDC unavailable from transfer failure, include health/problem/instance and BOOTSEL recovery instructions, emit no stale port variable.
- Optional recovery: consume only feat(windows): add scoped elevated USB PnP recovery helper #1148's typed request under explicit
--admin; after helper exit, repeat step 4 once without rebuilding or reflashing. Default/no-admin ends with manual guidance.
Completion evidence checklist
Before live flashing, install the candidate fbuild worktree editable into FastLED's venv and select an unused five-digit FBUILD_DAEMON_PORT; record that port so another checkout's daemon is untouched. Use only fbuild port scan and bash autoresearch rp2040 --rpc-smoke --timeout 120s --skip-lint for device evidence.
The final issue comment must include one healthy board run with its actual serial, pre/post scan health, transfer, returned port, and RPC smoke result. If a phantom state is reproduced, include the fail-closed run separately: it must show port=None/no stale environment port plus the exact recovery message. A historical phantom observation is context, not evidence that this PR works.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
zackees commentedon Jul 22, 2026
Tracked by meta #1149.
zackees commentedon Jul 23, 2026
[meta #1149] Closed via merged PR #1153 (squash merge 618001d, admin-merged per #1151 precedent on the known-red board-build matrix).
Validated (local, toolchain 1.94.1): soldr-prefixed tests — fbuild-deploy 241 passed; fbuild-daemon --lib 203 passed; fbuild-cli all suites ok; fbuild-serial unit+doc ok after the no_run fix; four-crate clippy -D warnings clean. CI: Formatting, Check (ubuntu/macos/windows), subprocess-spawn lint, VID/PID guard all green on the branch after fixing six pre-existing main-red gate failures rolled into the PR (commits 0964799..3e7cb4d, incl. the dylint @toolchain stale-alias workflow bug).
State transitions: select_cdc_candidate/resolve_requested_runtime_target health gates; bounded open probe in wait_for_cdc_port_with_clock; flash-vs-CDC-recovery split with port=None + BOOT/RESET guidance; daemon resolve_recovery_port no longer substitutes the pre-flash name when the deployer owns discovery; choose_deploy_port partitions known-unhealthy RP2040 records with diagnostics.
Callers intentionally left raw: non-RaspberryPi choose_deploy_port arms (generic platforms, #1091-adjacent), Deployer::post_deploy_recovery advisory poll, DeviceInfo/DeviceStatusResponse mirrors (typed field lands with #1152).
Hardware: NOT touched — the attached board (serial 5303284720C4641C) remains preserved in the reproduced wedge (BOOTSEL RPI-RP2 on G:, phantom COM12, PICOBOOT MI_01 CM_PROB_FAILED_INSTALL 28) for the fbuild-only closeout run. The fail-closed + recovery hardware evidence will be captured together with #1152 --admin orchestration on this exact wedge and posted here.
Next child: #1152.
feat(windows): orchestrate one-shot RP2040 USB recovery through deplo…
feat(autoresearch): warn when USB selective suspend can drop a HIL bo…