.pr_agent_accepted_suggestions - iNavFlight/inav GitHub Wiki

                     PR 11854 (2026-09-02)                    
[correctness] Dynamic notch silently disabled
Dynamic notch silently disabled RP2350 compiles and exposes dynamic gyro notch filtering, but its FFT and vector-math functions return success without initializing or modifying their outputs. Enabling the setting therefore runs the flight-control filter using nonexistent frequency analysis instead of the requested dynamic notch behavior.

Issue description

RP2350 supplies no-op implementations of the CMSIS DSP functions used by dynamic gyro notch analysis. Although the target disables the filter only by default, users can re-enable it and receive silently invalid filtering.

Issue Context

Either provide functional ARMv8-M-compatible DSP implementations or compile USE_DYNAMIC_FILTERS out for RP2350 so the unsupported setting and execution path cannot be enabled.

Fix Focus Areas

  • src/main/drivers/system_rp2350.c[220-287]
  • src/main/target/RP2350_PICO/target.h[150-175]
  • src/main/target/RP2350_PICO/config.c[24-31]
  • src/main/flight/gyroanalyse.c[72-89]


                     PR 11846 (2026-08-31)                    
[correctness] Upgrade corrupts alternate layouts
Upgrade corrupts alternate layouts Appending `OSD_TERRAIN_AGL` changes every row's stride in the persisted two-dimensional `item_pos` array, but the unchanged parameter-group version causes old bytes to be copied directly into the enlarged structure. After upgrade, layout 0's first value is consumed as the new terrain slot and every subsequent alternate-layout item is shifted, changing positions/visibility and assigning values to the wrong OSD elements.

Issue description

Adding an item changes the stride of each row in persisted item_pos[OSD_LAYOUT_COUNT][OSD_ITEM_COUNT]. Because the registered PG version remains 3, old four-layout payloads are copied as a flat prefix into the new shape and alternate layouts become misaligned.

Issue Context

The generic PG loader only performs a size-limited memcpy when versions match; it does not migrate two-dimensional arrays. Either bump the layout PG version so incompatible data resets safely, or add an explicit migration that copies each old layout row into the matching new row and initializes the Terrain AGL slot.

Fix Focus Areas

  • src/main/io/osd.h[382-383]
  • src/main/io/osd.c[235-236]


                     PR 11844 (2026-08-31)                    
[correctness] Stale branch base reset
Stale branch base reset `ConditionStack.closed` remains valid after intervening enumerators, so a later opposite-polarity block for the same symbol is incorrectly treated as the prior block's alternate and resets `current_numeric` to a stale base. For example, after `A, #ifdef X B, #endif C = 10, #ifndef X D`, this PR emits `D` as `(1)` instead of the correct `(11)`, whereas the old sequential counter handled the explicit reset correctly.

Issue description

The sibling-branch cache survives ordinary enum members, causing a later opposite-polarity conditional to reuse a stale numeric base even though the two blocks are not adjacent alternate branches.

Issue Context

Only an immediately corresponding opposite-polarity sibling should reuse the closed branch base. Parsing any intervening enumerator must invalidate the closed frame at that nesting level; add regression coverage including an intervening explicit assignment.

Fix Focus Areas

  • docs/development/msp/gen_enum_md.py[107-114]
  • docs/development/msp/gen_enum_md.py[262-297]


                     PR 11838 (2026-08-29)                    
[correctness] Circular radius omission accepted
Circular radius omission accepted `MSP2_INAV_SET_GEOZONE_VERTEX` now accepts a 10–13 byte payload for a circular zone, writes the center, skips the required 4-byte radius, and returns success. The resulting circular geozone retains no newly supplied radius (or a stale prior one), so enforcement can use an unintended boundary.

Issue description

MSP2_INAV_SET_GEOZONE_VERTEX accepts a circular-zone payload without the required four-byte radius, mutates the center, and reports success.

Issue Context

Polygon updates require a 10-byte prefix, while circular updates require that prefix plus a 4-byte radius. Preserve lenient acceptance of bytes beyond the known complete encoding, but reject circular payloads shorter than 14 bytes before mutating vertex state.

Fix Focus Areas

  • src/main/fc/fc_msp.c[3922-3936]


                     PR 11835 (2026-08-29)                    
[reliability] Release notes evade pruning
Release notes evade pruning `list_per_commit_baselines` anchors its branch capture against the entire release body, but `publish_asset` creates a two-line body, so normal per-commit releases produce no jq record and are invisible to pruning. The documented per-branch and global caps therefore do not bound repository growth.

Issue description

The pruning query attempts to match a one-line anchored regex against the complete multi-line release body, causing releases created by this script to be omitted.

Issue Context

Extract and validate the first body line, and ensure malformed notes still emit a record under the fallback branch group rather than disappearing from the stream.

Fix Focus Areas

  • .github/scripts/publish-size-baseline.sh[74-76]
  • .github/scripts/publish-size-baseline.sh[87-92]

[reliability] Old baselines are never deleted
Old baselines are never deleted The first AWK pass stores only the newest 50 tags per branch in `keepTag`, and the `END` block iterates only `keepTag`, so tags beyond the first 50 can never be printed for deletion. Even after fixing release-note parsing, the per-branch cap is not enforced and old releases accumulate indefinitely unless they happen to be selected for a separate global-cap deletion.

Issue description

The pruning AWK program only considers retained tags when producing deletions and loses every tag outside the per-branch keep set.

Issue Context

Track every input tag, retain only the newest configured count per branch and then the global count, and print every input tag absent from the final keep set.

Fix Focus Areas

  • .github/scripts/publish-size-baseline.sh[99-120]


                     PR 11831 (2026-08-26)                    
[correctness] Wrapped COG spikes D-term
Wrapped COG spikes D-term Without `PID_DTERM_FROM_ERROR`, `navPidApply2` differentiates raw course over ground, so a normal 359.99°→0° crossing is treated as an approximately −360° step and produces a large roll-command transient. The fixed-wing navigation D gain is enabled by default, and output clamping only turns this spike into a maximum-bank command rather than preventing it.

Issue description

The new derivative-on-measurement path differentiates wrapped COG directly. Crossing 0°/360° therefore creates an artificial full-circle delta and a large D-term roll correction.

Issue Context

navHeadingError is wrapped safely, but actualState.cog is represented as a wrapped angular value. Preserve derivative-on-measurement while making the measurement delta wrap-aware, for example by maintaining a continuous/unwrapped COG value or adding an angular-difference-aware PID input path; reset that state whenever the navigation PID is reset.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing.c[555-561]
  • src/main/common/fp_pid.c[60-73]
  • src/main/navigation/navigation_pos_estimator.c[742-752]


                     PR 11827 (2026-08-26)                    
[correctness] LED buffer sizes use wrong width
LED buffer sizes use wrong width The WS2812 example calculates both the old 6,230-byte array and the 384-byte circular buffer using 2-byte elements, but this codebase defines `timerDMASafeType_t` as 4 bytes on every supported F4/F7/H7/AT32 family, making those buffers 12,460 and 768 bytes respectively. This also conflicts with the guide's later instruction to retain `timerDMASafeType_t`, so contributors cannot reproduce the stated RAM figures.

Issue description

The WS2812 RAM figures assume 2-byte DMA elements even though the supported timer DMA-safe type is 4 bytes.

Issue Context

The full array has 3,115 elements and the proposed 2×4-LED circular buffer has 192 elements. Keep the stated byte counts consistent with the guide's later requirement to use timerDMASafeType_t.

Fix Focus Areas

  • docs/development/ram-and-flash-optimization.md[52-55]


                     PR 11788 (2026-08-17)                    
[maintainability] Stale enum protocol docs
Stale enum protocol docs The PR changes wire values of `resolutionType_e` (e.g., `HD_6022` becomes 3 and `HD_5320` becomes 4, adding `HD_3016=2`), but the committed MSP enum reference docs still list the old ordinals. This leaves the repository’s protocol documentation inconsistent with the firmware, risking mis-implementation by tooling/third parties relying on these references.

Issue description

resolutionType_e is a wire-protocol enum (sent as a raw byte via MSP_DP_OPTIONS). This PR updates its explicit values, but the committed enum reference artifacts under docs/development/msp/ still document the old ordinals, causing protocol documentation drift.

Issue Context

The repo contains auto-generated enum reference files (inav_enums.json, inav_enums_ref.md) produced by docs/development/msp/gen_docs.sh / gen_enum_md.py. These should reflect the updated resolutionType_e members and values (including the newly reintroduced HD_3016=2).

Fix Focus Areas

  • src/main/io/displayport_msp_osd.c[64-73]
  • docs/development/msp/inav_enums.json[3300-3306]
  • docs/development/msp/inav_enums_ref.md[4824-4834]

What to change

  • Regenerate/update docs/development/msp/inav_enums.json and docs/development/msp/inav_enums_ref.md so resolutionType_e matches the new wire values:
  • SD_3016 = 0
  • HD_5018 = 1
  • HD_3016 = 2
  • HD_6022 = 3
  • HD_5320 = 4
  • Prefer running the documented generator (docs/development/msp/gen_docs.sh) and committing the updated artifacts, rather than hand-editing.


                     PR 11782 (2026-08-13)                    
[correctness] Auto-close skips onExit
Auto-close skips onExit cmsUpdate() force-closes the in-flight CMS with cmsMenuExit(..., CMS_EXIT) on safety loss/timeout, but cmsMenuExit() does not run per-menu onExit callbacks for CMS_EXIT. This can leave temporary state enabled after the menu collapses (e.g., OSD layout preview override is only cleared in menuOsdElements.onExit).

Issue description

In-flight auto-close paths call cmsMenuExit(..., CMS_EXIT), but cmsMenuExit() does not dispatch onExit callbacks for CMS_EXIT. Menus that rely on onExit for cleanup (e.g. OSD layout preview override) can leave that transient state active after an emergency/timeout close.

Issue Context

  • In-flight main menu includes the OSD submenu (cmsx_menuOsd), which can enter menuOsdElements that sets an OSD override on enter and clears it on exit.
  • Safety close / inactivity close uses CMS_EXIT, not cmsMenuBack(), so no stack unwinding happens.

Fix Focus Areas

  • Ensure auto-close (safety/timeout/switch-off) triggers the same cleanup as backing out of menus:
  • Call currentCtx.menu->onExit(...) if present.
  • Optionally iterate menuStack[] and call onExit for each stacked menu (similar to the popup-save traversal) to guarantee cleanup.
  • Keep the “no EEPROM save/reboot while armed” restriction intact.
  • src/main/cms/cms.c[908-944]
  • src/main/cms/cms.c[1443-1457]

[reliability] batteryInit resets live state
batteryInit resets live state The battery CMS menus call batteryInit() when exiting while armed, which resets batteryState and voltage thresholds to zero. If throttle VBAT compensation is enabled, mixerThrottleCommand will use calculateThrottleCompensationFactor() based on batteryFullVoltage=0 until batteryUpdate recomputes, changing throttle scaling transiently.

Issue description

Calling batteryInit() while armed resets live battery runtime state (batteryState, cell count, and voltage thresholds). This can transiently change behaviors that depend on those values (notably throttle VBAT compensation which uses batteryFullVoltage).

Issue Context

The CMS battery menus already write settings directly; they only need to refresh derived thresholds/state, not reset battery presence.

Fix Focus Areas

  • Replace batteryInit() (armed path) with a new targeted refresh that:
  • recomputes batteryFullVoltage/batteryWarningVoltage/batteryCriticalVoltage based on current batteryCellCount and current battery profile, without forcing batteryState = BATTERY_NOT_PRESENT.
  • avoids transiently zeroing values used in the mixer.
  • If a new battery API is needed, implement it in sensors/battery.c and expose via sensors/battery.h.
  • src/main/cms/cms_menu_battery.c[60-68]
  • src/main/cms/cms_menu_battery.c[100-109]


                     PR 11771 (2026-08-08)                    
[correctness] MAVLink not transmit-only
MAVLink not transmit-only docs/Telemetry.md states MAVLink in INAV is “transmit-only”, but the current MAVLink telemetry implementation reads and processes incoming MAVLink messages (missions/params/RC override/etc). This misleads users about MAVLink capabilities and is in-scope because the PR edited this sentence but kept the incorrect claim.

Issue description

docs/Telemetry.md says MAVLink is transmit-only, but the implementation processes inbound MAVLink messages. This is incorrect documentation and can lead users to wrong configuration/expectations.

Issue Context

The telemetry MAVLink code contains an explicit receive loop and dispatches received message IDs.

Fix Focus Areas

  • docs/Telemetry.md[205-210]
  • src/main/telemetry/mavlink.c[1388-1433]
  • src/main/telemetry/mavlink.c[1444-1456]

[maintainability] SoftSerial baud docs conflict
SoftSerial baud docs conflict This PR removes the “softserial is limit to 19200” note from the iBus telemetry section, but other docs still state SoftSerial is limited to 19200 baud. This leaves contradictory guidance across the documentation set and will confuse users configuring SoftSerial-based telemetry/logging.

Issue description

After this PR, docs/Telemetry.md no longer mentions a 19200 SoftSerial limit for iBus, but docs/Serial.md and docs/Blackbox.md still claim SoftSerial is limited to 19200 baud. This is conflicting documentation.

Issue Context

The PR’s stated goal is to fix misleading SoftSerial-limit docs; this should be applied consistently across the documentation set.

Fix Focus Areas

  • docs/Telemetry.md[231-237]
  • docs/Serial.md[46-48]
  • docs/Blackbox.md[68-75]

[maintainability] Softserial limit docs conflict
Softserial limit docs conflict docs/Telemetry.md removes the “softserial limited to 19200” guidance, but other docs still claim SoftSerial is capped at 19200, creating contradictory configuration guidance. The SoftSerial implementation appears to accept an arbitrary baud (no explicit 19200 clamp), so the remaining “19200 limit” statements are likely misleading or at least need qualification as a practical recommendation rather than a hard limit.

Issue description

docs/Telemetry.md no longer states SoftSerial is limited to 19200 baud, but docs/Serial.md and docs/Blackbox.md still state (as a hard limit) that SoftSerial is limited to 19200. This creates contradictory documentation after this PR.

Issue Context

The SoftSerial code path takes a baud parameter and configures timers from it without a clear 19200 cap, so documentation that presents 19200 as a strict limit is inconsistent with the current implementation.

Fix Focus Areas

  • docs/Telemetry.md[205-236]
  • docs/Serial.md[36-49]
  • docs/Blackbox.md[68-76]
  • src/main/drivers/serial_softserial.c[170-189]
  • src/main/drivers/serial_softserial.c[204-345]

Suggested change

Update the other docs to match the new reality (e.g., remove the “limited to 19200” hard-limit phrasing) or rephrase consistently as a practical/recommended limit if that’s the intended guidance (e.g., “SoftSerial may be unreliable at high baud rates; prefer hardware UART for >19200 / high bandwidth”).


[maintainability] MAVLink sentence punctuation
MAVLink sentence punctuation The new sentence “transmit-only, is usable…” is a comma splice and reads ambiguously. This reduces clarity in the updated documentation.

Issue description

The updated MAVLink line uses a comma splice (“transmit-only, is usable”), which is grammatically awkward and can be misread.

Issue Context

This is purely a documentation clarity/readability fix.

Fix Focus Areas

  • docs/Telemetry.md[205-210]

Suggested change

Reword to something like:

  • “MAVLink implementation in INAV is transmit-only and usable at low baud rates. MAVLink V1 and V2 are supported.”

[correctness] MAVLink not transmit-only
MAVLink not transmit-only docs/Telemetry.md states MAVLink in INAV is “transmit-only”, but the current MAVLink telemetry implementation reads and processes incoming MAVLink messages (missions/params/RC override/etc). This misleads users about MAVLink capabilities and is in-scope because the PR edited this sentence but kept the incorrect claim.

Issue description

docs/Telemetry.md says MAVLink is transmit-only, but the implementation processes inbound MAVLink messages. This is incorrect documentation and can lead users to wrong configuration/expectations.

Issue Context

The telemetry MAVLink code contains an explicit receive loop and dispatches received message IDs.

Fix Focus Areas

  • docs/Telemetry.md[205-210]
  • src/main/telemetry/mavlink.c[1388-1433]
  • src/main/telemetry/mavlink.c[1444-1456]

[maintainability] Softserial limit docs conflict
Softserial limit docs conflict docs/Telemetry.md removes the “softserial limited to 19200” guidance, but other docs still claim SoftSerial is capped at 19200, creating contradictory configuration guidance. The SoftSerial implementation appears to accept an arbitrary baud (no explicit 19200 clamp), so the remaining “19200 limit” statements are likely misleading or at least need qualification as a practical recommendation rather than a hard limit.

Issue description

docs/Telemetry.md no longer states SoftSerial is limited to 19200 baud, but docs/Serial.md and docs/Blackbox.md still state (as a hard limit) that SoftSerial is limited to 19200. This creates contradictory documentation after this PR.

Issue Context

The SoftSerial code path takes a baud parameter and configures timers from it without a clear 19200 cap, so documentation that presents 19200 as a strict limit is inconsistent with the current implementation.

Fix Focus Areas

  • docs/Telemetry.md[205-236]
  • docs/Serial.md[36-49]
  • docs/Blackbox.md[68-76]
  • src/main/drivers/serial_softserial.c[170-189]
  • src/main/drivers/serial_softserial.c[204-345]

Suggested change

Update the other docs to match the new reality (e.g., remove the “limited to 19200” hard-limit phrasing) or rephrase consistently as a practical/recommended limit if that’s the intended guidance (e.g., “SoftSerial may be unreliable at high baud rates; prefer hardware UART for >19200 / high bandwidth”).


[maintainability] SoftSerial baud docs conflict
SoftSerial baud docs conflict This PR removes the “softserial is limit to 19200” note from the iBus telemetry section, but other docs still state SoftSerial is limited to 19200 baud. This leaves contradictory guidance across the documentation set and will confuse users configuring SoftSerial-based telemetry/logging.

Issue description

After this PR, docs/Telemetry.md no longer mentions a 19200 SoftSerial limit for iBus, but docs/Serial.md and docs/Blackbox.md still claim SoftSerial is limited to 19200 baud. This is conflicting documentation.

Issue Context

The PR’s stated goal is to fix misleading SoftSerial-limit docs; this should be applied consistently across the documentation set.

Fix Focus Areas

  • docs/Telemetry.md[231-237]
  • docs/Serial.md[46-48]
  • docs/Blackbox.md[68-75]

[maintainability] Softserial limit docs conflict
Softserial limit docs conflict docs/Telemetry.md removes the “softserial limited to 19200” guidance, but other docs still claim SoftSerial is capped at 19200, creating contradictory configuration guidance. The SoftSerial implementation appears to accept an arbitrary baud (no explicit 19200 clamp), so the remaining “19200 limit” statements are likely misleading or at least need qualification as a practical recommendation rather than a hard limit.

Issue description

docs/Telemetry.md no longer states SoftSerial is limited to 19200 baud, but docs/Serial.md and docs/Blackbox.md still state (as a hard limit) that SoftSerial is limited to 19200. This creates contradictory documentation after this PR.

Issue Context

The SoftSerial code path takes a baud parameter and configures timers from it without a clear 19200 cap, so documentation that presents 19200 as a strict limit is inconsistent with the current implementation.

Fix Focus Areas

  • docs/Telemetry.md[205-236]
  • docs/Serial.md[36-49]
  • docs/Blackbox.md[68-76]
  • src/main/drivers/serial_softserial.c[170-189]
  • src/main/drivers/serial_softserial.c[204-345]

Suggested change

Update the other docs to match the new reality (e.g., remove the “limited to 19200” hard-limit phrasing) or rephrase consistently as a practical/recommended limit if that’s the intended guidance (e.g., “SoftSerial may be unreliable at high baud rates; prefer hardware UART for >19200 / high bandwidth”).


[maintainability] MAVLink sentence punctuation
MAVLink sentence punctuation The new sentence “transmit-only, is usable…” is a comma splice and reads ambiguously. This reduces clarity in the updated documentation.

Issue description

The updated MAVLink line uses a comma splice (“transmit-only, is usable”), which is grammatically awkward and can be misread.

Issue Context

This is purely a documentation clarity/readability fix.

Fix Focus Areas

  • docs/Telemetry.md[205-210]

Suggested change

Reword to something like:

  • “MAVLink implementation in INAV is transmit-only and usable at low baud rates. MAVLink V1 and V2 are supported.”

[maintainability] MAVLink sentence punctuation
MAVLink sentence punctuation The new sentence “transmit-only, is usable…” is a comma splice and reads ambiguously. This reduces clarity in the updated documentation.

Issue description

The updated MAVLink line uses a comma splice (“transmit-only, is usable”), which is grammatically awkward and can be misread.

Issue Context

This is purely a documentation clarity/readability fix.

Fix Focus Areas

  • docs/Telemetry.md[205-210]

Suggested change

Reword to something like:

  • “MAVLink implementation in INAV is transmit-only and usable at low baud rates. MAVLink V1 and V2 are supported.”


                     PR 11769 (2026-08-08)                    
[correctness] Invalid RSSI becomes -255
Invalid RSSI becomes -255 In mavlinkParseRxStats() GENERIC mode now negates msg->rssi without handling MAVLink’s invalid/unknown sentinel (UINT8_MAX/255), producing uplinkRSSI = -255. This value is consumed as a real dBm reading (e.g., by OSD alarm checks), causing false low-RSSI alarms/logic triggers when RSSI is actually unknown.

Issue description

mavlinkParseRxStats() in GENERIC mode unconditionally assigns rxLinkStatistics.uplinkRSSI = -msg->rssi;. MAVLink defines rssi == UINT8_MAX (255) as invalid/unknown, so the new code turns this into -255, which downstream code interprets as an extremely poor (but valid) dBm.

Issue Context

  • MAVLink RADIO_STATUS.rssi is uint8_t with values [0..254], 255 = invalid/unknown.
  • The same function already treats msg->rssi != 255 specially for LQ, indicating the sentinel is expected.
  • OSD displays rxLinkStatistics.uplinkRSSI directly and compares it to osdConfig()->rssi_dbm_alarm, so -255 can trigger alarms even though RSSI is unknown.

Fix Focus Areas

  • src/main/telemetry/mavlink.c[1318-1328]

Suggested change

  • Only negate when msg->rssi != UINT8_MAX; otherwise set uplinkRSSI to a defined “unknown” representation that won’t be misinterpreted as a real low dBm (e.g. 0, or a dedicated sentinel plus consumer handling).
  • Make the cast explicit to avoid any ambiguity: -(int16_t)msg->rssi.

[correctness] Invalid RSSI becomes -255
Invalid RSSI becomes -255 In mavlinkParseRxStats() GENERIC mode now negates msg->rssi without handling MAVLink’s invalid/unknown sentinel (UINT8_MAX/255), producing uplinkRSSI = -255. This value is consumed as a real dBm reading (e.g., by OSD alarm checks), causing false low-RSSI alarms/logic triggers when RSSI is actually unknown.

Issue description

mavlinkParseRxStats() in GENERIC mode unconditionally assigns rxLinkStatistics.uplinkRSSI = -msg->rssi;. MAVLink defines rssi == UINT8_MAX (255) as invalid/unknown, so the new code turns this into -255, which downstream code interprets as an extremely poor (but valid) dBm.

Issue Context

  • MAVLink RADIO_STATUS.rssi is uint8_t with values [0..254], 255 = invalid/unknown.
  • The same function already treats msg->rssi != 255 specially for LQ, indicating the sentinel is expected.
  • OSD displays rxLinkStatistics.uplinkRSSI directly and compares it to osdConfig()->rssi_dbm_alarm, so -255 can trigger alarms even though RSSI is unknown.

Fix Focus Areas

  • src/main/telemetry/mavlink.c[1318-1328]

Suggested change

  • Only negate when msg->rssi != UINT8_MAX; otherwise set uplinkRSSI to a defined “unknown” representation that won’t be misinterpreted as a real low dBm (e.g. 0, or a dedicated sentinel plus consumer handling).
  • Make the cast explicit to avoid any ambiguity: -(int16_t)msg->rssi.

[correctness] Invalid RSSI becomes -255
Invalid RSSI becomes -255 In mavlinkParseRxStats() GENERIC mode now negates msg->rssi without handling MAVLink’s invalid/unknown sentinel (UINT8_MAX/255), producing uplinkRSSI = -255. This value is consumed as a real dBm reading (e.g., by OSD alarm checks), causing false low-RSSI alarms/logic triggers when RSSI is actually unknown.

Issue description

mavlinkParseRxStats() in GENERIC mode unconditionally assigns rxLinkStatistics.uplinkRSSI = -msg->rssi;. MAVLink defines rssi == UINT8_MAX (255) as invalid/unknown, so the new code turns this into -255, which downstream code interprets as an extremely poor (but valid) dBm.

Issue Context

  • MAVLink RADIO_STATUS.rssi is uint8_t with values [0..254], 255 = invalid/unknown.
  • The same function already treats msg->rssi != 255 specially for LQ, indicating the sentinel is expected.
  • OSD displays rxLinkStatistics.uplinkRSSI directly and compares it to osdConfig()->rssi_dbm_alarm, so -255 can trigger alarms even though RSSI is unknown.

Fix Focus Areas

  • src/main/telemetry/mavlink.c[1318-1328]

Suggested change

  • Only negate when msg->rssi != UINT8_MAX; otherwise set uplinkRSSI to a defined “unknown” representation that won’t be misinterpreted as a real low dBm (e.g. 0, or a dedicated sentinel plus consumer handling).
  • Make the cast explicit to avoid any ambiguity: -(int16_t)msg->rssi.


                     PR 11766 (2026-08-07)                    
[correctness] HRST counter never resets
HRST counter never resets hottPrepareGPSResponse() uses a function-static hrstSent counter to limit BOXHOMERESET (“HRST”) display, but never resets it when the mode is released. After it reaches the limit once, later home-reset activations will never show HRST again until reboot.

Issue description

hrstSent is a function-static counter used to rate-limit the "HRST" indication when BOXHOMERESET is active, but it is never reset when the mode becomes inactive. This permanently disables the HRST indication after the first limited run.

Issue Context

A similar flight-mode text implementation resets hrstSent when BOXHOMERESET is not active, allowing each new activation to show the indication again.

Fix Focus Areas

  • src/main/telemetry/hott.c[229-382]

Implementation notes

  • After the flight-mode selection logic, add something like:
  • if (!IS_RC_MODE_ACTIVE(BOXHOMERESET) && hrstSent > 0) hrstSent = 0;
  • Optionally also reset hrstSent when disarmed to avoid carrying state across arm cycles.

[correctness] Stale free-char status
Stale free-char status hottPrepareGPSResponse() still returns early when there is no GPS fix, but the PR’s new free_char1..3 status text is set after that return and is not cleared per call. After a GPS fix drop, the HoTT GPS message can continue transmitting stale free_char values from a previous state.

Issue description

The function returns early on !STATE(GPS_FIX) (or !STATE(GPS_FIX) && !STATE(GPS_ESTIMATED_FIX)), skipping the newly added free_char status updates. Because the message struct is reused across calls (not memset each time), the free_char bytes can retain stale values after GPS fix loss.

Issue Context

initialiseGPSMessage() zeros the struct once at init; hottPrepareGPSResponse() mutates fields in-place and has an early return on no-fix.

Fix Focus Areas

  • src/main/telemetry/hott.c[180-187]
  • src/main/telemetry/hott.c[229-381]

Implementation notes

Choose one of:

  1. Set a safe default for free_char1..3 (e.g. blanks or "---" / "NOF") before the GPS-fix early return and also set them in the no-fix branch.
  2. Move the free_char flight-mode/status computation above the GPS-fix early return so it always runs. Also consider clearing home_direction/flight_direction in the no-fix path to avoid other stale fields.

[correctness] HRST counter never resets
HRST counter never resets hottPrepareGPSResponse() uses a function-static hrstSent counter to limit BOXHOMERESET (“HRST”) display, but never resets it when the mode is released. After it reaches the limit once, later home-reset activations will never show HRST again until reboot.

Issue description

hrstSent is a function-static counter used to rate-limit the "HRST" indication when BOXHOMERESET is active, but it is never reset when the mode becomes inactive. This permanently disables the HRST indication after the first limited run.

Issue Context

A similar flight-mode text implementation resets hrstSent when BOXHOMERESET is not active, allowing each new activation to show the indication again.

Fix Focus Areas

  • src/main/telemetry/hott.c[229-382]

Implementation notes

  • After the flight-mode selection logic, add something like:
  • if (!IS_RC_MODE_ACTIVE(BOXHOMERESET) && hrstSent > 0) hrstSent = 0;
  • Optionally also reset hrstSent when disarmed to avoid carrying state across arm cycles.

[correctness] Stale free-char status
Stale free-char status hottPrepareGPSResponse() still returns early when there is no GPS fix, but the PR’s new free_char1..3 status text is set after that return and is not cleared per call. After a GPS fix drop, the HoTT GPS message can continue transmitting stale free_char values from a previous state.

Issue description

The function returns early on !STATE(GPS_FIX) (or !STATE(GPS_FIX) && !STATE(GPS_ESTIMATED_FIX)), skipping the newly added free_char status updates. Because the message struct is reused across calls (not memset each time), the free_char bytes can retain stale values after GPS fix loss.

Issue Context

initialiseGPSMessage() zeros the struct once at init; hottPrepareGPSResponse() mutates fields in-place and has an early return on no-fix.

Fix Focus Areas

  • src/main/telemetry/hott.c[180-187]
  • src/main/telemetry/hott.c[229-381]

Implementation notes

Choose one of:

  1. Set a safe default for free_char1..3 (e.g. blanks or "---" / "NOF") before the GPS-fix early return and also set them in the no-fix branch.
  2. Move the free_char flight-mode/status computation above the GPS-fix early return so it always runs. Also consider clearing home_direction/flight_direction in the no-fix path to avoid other stale fields.

[correctness] HRST counter never resets
HRST counter never resets hottPrepareGPSResponse() uses a function-static hrstSent counter to limit BOXHOMERESET (“HRST”) display, but never resets it when the mode is released. After it reaches the limit once, later home-reset activations will never show HRST again until reboot.

Issue description

hrstSent is a function-static counter used to rate-limit the "HRST" indication when BOXHOMERESET is active, but it is never reset when the mode becomes inactive. This permanently disables the HRST indication after the first limited run.

Issue Context

A similar flight-mode text implementation resets hrstSent when BOXHOMERESET is not active, allowing each new activation to show the indication again.

Fix Focus Areas

  • src/main/telemetry/hott.c[229-382]

Implementation notes

  • After the flight-mode selection logic, add something like:
  • if (!IS_RC_MODE_ACTIVE(BOXHOMERESET) && hrstSent > 0) hrstSent = 0;
  • Optionally also reset hrstSent when disarmed to avoid carrying state across arm cycles.

[correctness] Stale free-char status
Stale free-char status hottPrepareGPSResponse() still returns early when there is no GPS fix, but the PR’s new free_char1..3 status text is set after that return and is not cleared per call. After a GPS fix drop, the HoTT GPS message can continue transmitting stale free_char values from a previous state.

Issue description

The function returns early on !STATE(GPS_FIX) (or !STATE(GPS_FIX) && !STATE(GPS_ESTIMATED_FIX)), skipping the newly added free_char status updates. Because the message struct is reused across calls (not memset each time), the free_char bytes can retain stale values after GPS fix loss.

Issue Context

initialiseGPSMessage() zeros the struct once at init; hottPrepareGPSResponse() mutates fields in-place and has an early return on no-fix.

Fix Focus Areas

  • src/main/telemetry/hott.c[180-187]
  • src/main/telemetry/hott.c[229-381]

Implementation notes

Choose one of:

  1. Set a safe default for free_char1..3 (e.g. blanks or "---" / "NOF") before the GPS-fix early return and also set them in the no-fix branch.
  2. Move the free_char flight-mode/status computation above the GPS-fix early return so it always runs. Also consider clearing home_direction/flight_direction in the no-fix path to avoid other stale fields.


                     PR 11765 (2026-08-06)                    
[reliability] Retry loop off-by-one
Retry loop off-by-one icm40609dDeviceDetect() uses a uint8_t post-decrement do/while which performs 6 WHO_AM_I reads for attemptsRemaining=5, adding an extra 150ms delay and making retry behavior misleading.

Issue description

The retry loop in icm40609dDeviceDetect() runs one extra time due to do { ... } while (attemptsRemaining--); with an unsigned counter. This adds an extra delay on failed detection and obscures intent.

Issue Context

This does not cause an infinite loop, but it does perform 6 attempts when initialized to 5.

Fix Focus Areas

  • src/main/drivers/accgyro/accgyro_icm40609d.c[174-198]

Suggested fix approach

Replace with an explicit bounded loop, e.g.:


[reliability] Retry loop off-by-one
Retry loop off-by-one icm40609dDeviceDetect() uses a uint8_t post-decrement do/while which performs 6 WHO_AM_I reads for attemptsRemaining=5, adding an extra 150ms delay and making retry behavior misleading.

Issue description

The retry loop in icm40609dDeviceDetect() runs one extra time due to do { ... } while (attemptsRemaining--); with an unsigned counter. This adds an extra delay on failed detection and obscures intent.

Issue Context

This does not cause an infinite loop, but it does perform 6 attempts when initialized to 5.

Fix Focus Areas

  • src/main/drivers/accgyro/accgyro_icm40609d.c[174-198]

Suggested fix approach

Replace with an explicit bounded loop, e.g.:



                     PR 11759 (2026-08-02)                    
[correctness] Stale wind values
Stale wind values MSP2_INAV_WIND always serializes getEstimatedHorizontalWindSpeed() even when isEstimatedWindSpeedValid() is false, so it can return a previous (stale) non-zero estimate after the estimator invalidates on timeout. This violates the new MSP message contract that says speed/angle are 0 when unavailable and can mislead MSP consumers that don’t strictly gate on the validity bit.

Issue description

MSP2_INAV_WIND currently writes wind speed/angle values unconditionally, and only sets a validity flag afterward. Because the wind estimator can become invalid without clearing the last estimated wind vector, the MSP reply can contain non-zero (stale) values while flags.bit0 == 0, contradicting the documented contract (“0 if unavailable”).

Issue Context

  • getEstimatedHorizontalWindSpeed() computes magnitude/angle from cached estimatedWind[] regardless of validity.
  • The estimator invalidation path clears hasValidWindEstimate but does not reset estimatedWind[].
  • The MSP docs for MSP2_INAV_WIND state the fields are zero when unavailable.

Fix Focus Areas

  • src/main/fc/fc_msp.c[1603-1618]

Recommended implementation approach

  • Check isEstimatedWindSpeedValid() first.
  • If invalid: write windSpeed=0, windAngle=0, flags=0.
  • If valid: compute and write speed/angle, set flags=1.
  • Optionally clamp the float speed to [0, UINT16_MAX] before casting to avoid wrap/truncation surprises.


                     PR 11757 (2026-08-01)                    
[correctness] Waypoint speed sign wrap
Waypoint speed sign wrap getActiveSpeed() reads signed waypoint params (p1/p2 are int16_t) into a uint16_t and returns it directly for AIRPLANE, so negative/sentinel values wrap to large positive speeds. In WP-enabled Auto Speed, this wrapped value is then constrained, effectively commanding max Auto Speed unexpectedly.

Issue description

getActiveSpeed() assigns navWaypoint_t.p1/p2 (signed int16_t) to a uint16_t temporary and returns it for AIRPLANE without validating it. Negative values (e.g. -1) become large unsigned values (e.g. 65535) and later get clamped to maxSpeed, producing an unintended full-speed demand.

Issue Context

Waypoint params are stored in navWaypoint_t as signed integers. The fixed-wing WP Auto Speed path uses getActiveSpeed() to derive speed demand and constrains it between fw_auto_speed_min_speed and fw_auto_speed_max_speed.

Fix Focus Areas

  • src/main/navigation/navigation.c[4321-4353]
  • src/main/navigation/navigation.h[553-560]
  • src/main/navigation/navigation_fixedwing.c[939-948]

Proposed fix

  • Keep waypoint speed as a signed type (int16_t) when reading p1/p2.
  • Treat <= 0 as “no WP speed set” (return 0 or fall back to configured default).
  • Optionally clamp to sane bounds before returning (e.g., MIN(wpSpeed, navConfig()->general.max_auto_speed) for MR; for FW, either clamp or rely on the later constrain but only after ensuring non-negative).

[maintainability] Autospeed flag has linkage
Autospeed flag has linkage autoSpeedIsActive is introduced as a non-static file-scope variable even though it is only used within navigation_fixedwing.c. This unnecessarily exports a new global symbol and increases the risk of future linkage collisions or accidental external modification.

Issue description

autoSpeedIsActive is defined at file scope without static, giving it external linkage even though it is only referenced inside navigation_fixedwing.c.

Issue Context

There is already a public accessor isFixedwingAutoSpeedActive(), so the backing flag does not need external linkage.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing.c[74-78]

Proposed fix

Change the definition to static bool autoSpeedIsActive = false; (or move it into a private module struct) to keep the symbol local to the translation unit.



                     PR 11755 (2026-07-31)                    
[reliability] PWM beeper not enabled
PWM beeper not enabled The target defines BEEPER_PWM_FREQUENCY and provides a TIM_USE_BEEPER timer mapping on PA15, but targetConfiguration() never enables beeperConfigMutable()->pwmMode, so the PWM beeper path is never initialized. On boards with a passive beeper (which requires PWM to generate tone), this results in a non-functional beeper despite the PWM configuration being present.

Issue description

The target defines PWM-beeper parameters (BEEPER_PWM_FREQUENCY and a TIM_USE_BEEPER timer mapping), but targetConfiguration() does not enable beeperConfig()->pwmMode. As a result, beeperPwmInit() is not called and the timer-based beeper output is never configured.

Issue Context

sound_beeper.c only initializes PWM beeper when beeperConfig()->pwmMode is true; otherwise it uses GPIO toggling.

Fix Focus Areas

  • src/main/target/HUMMINGBIRD_FC305_H7/config.c[18-29]

Suggested fix

  1. Include fc/config.h in config.c.
  2. Set beeperConfigMutable()->pwmMode = true; in targetConfiguration() (if the hardware is passive / intended to use PWM).
  3. If the hardware is actually an active buzzer, remove the PWM-specific configuration (timer mapping / frequency) to avoid confusion.

[correctness] Board ID collision
Board ID collision HUMMINGBIRD_FC305_H7 sets TARGET_BOARD_IDENTIFIER to "HBRD", which is already used by HUMMINGBIRD_FC305, making the MSP-reported boardIdentifier ambiguous for tooling that keys off this field. This can cause target mis-identification when selecting board-specific defaults/resources or in support/debug workflows.

Issue description

TARGET_BOARD_IDENTIFIER is duplicated between two different targets (HUMMINGBIRD_FC305 and HUMMINGBIRD_FC305_H7). Since MSP exports this identifier, tools/users relying on boardIdentifier cannot reliably distinguish which firmware/target is running.

Issue Context

The new target defines TARGET_BOARD_IDENTIFIER "HBRD", but the existing HUMMINGBIRD_FC305 target already uses the same identifier.

Fix Focus Areas

  • src/main/target/HUMMINGBIRD_FC305_H7/target.h[20-22]

Suggested fix

Change TARGET_BOARD_IDENTIFIER for HUMMINGBIRD_FC305_H7 to a unique (ideally 4-character, uppercase) identifier, and ensure any documentation/release tooling that references the identifier is updated accordingly.



                     PR 11688 (2026-07-06)                    
[correctness] Reboots churn persisted node IDs
Reboots churn persisted node IDs When a known peripheral restarts within the 10-second NodeStatus stale window, `dnaLookupOrAssignNode()` treats that peripheral's own cached node ID as an active conflict and overwrites its persisted allocation with a new ID. Repeated quick restarts can therefore consume successive IDs and trigger unnecessary configuration saves instead of returning the stable mapping.

Issue description

A rebooting peripheral can be mistaken for a conflicting live node because its previous NodeStatus entry remains cached for 10 seconds. This causes its persisted node ID to be replaced during rapid restart.

Issue Context

The live-node table contains only node IDs and retains them until the stale timeout, so presence in that table does not prove another node is still broadcasting. Preserve the stored mapping unless the occupant is verified as a different currently-live node, or defer allocation until the cached entry expires.

Fix Focus Areas

  • src/main/drivers/dronecan/dronecan_dna_server.c[177-192]
  • src/main/drivers/dronecan/dronecan_node_status.c[150-161]
  • src/main/drivers/dronecan/dronecan.h[58-58]


                     PR 11683 (2026-07-02)                    
[reliability] Timeout checked before RX
Timeout checked before RX dronecanUpdate() calls dronecanAsyncCheckTimeout() before draining the CAN RX FIFO, so a response already queued can be discarded as ERROR when the millis() delta crosses the threshold in that tick. Because the response handler ignores non-PENDING slots, this can produce false timeouts even when the response arrived within DRONECAN_ASYNC_TIMEOUT_MS.

Issue description

dronecanAsyncCheckTimeout() is executed before processing queued RX frames in dronecanUpdate(). If a response arrives just before the timeout boundary but the next dronecanUpdate() tick occurs at/after the boundary, the slot can be marked ERROR and the queued response will then be ignored by dronecanAsyncHandleServiceResponse() (it only processes when state==PENDING).

Issue Context

The timeout logic uses millis() granularity, so boundary effects are realistic. A correct implementation should always give queued RX frames a chance to resolve the slot before expiring it.

Fix Focus Areas

  • src/main/drivers/dronecan/dronecan.c[145-168]
  • src/main/drivers/dronecan/dronecan_async.c[159-168]

Suggested fix

Reorder the NORMAL-state loop so it drains RX (and any resulting TX) first, then calls dronecanAsyncCheckTimeout() after RX handling. Optionally, call the timeout check once per loop iteration after RX+TX drain, so a response in the FIFO always wins over expiring the slot.



                     PR 11681 (2026-07-02)                    
[correctness] AAF freq overflow
AAF freq overflow In getGyroAafConfig(), selectedFreq is stored as int8_t but the AAF lookup table contains frequencies like 258 and 303, so selectedFreq overflows and can corrupt the “closest frequency” comparison, selecting the wrong AAF parameters for ICM42688P/ICM42686P.

Issue description

getGyroAafConfig() uses int8_t selectedFreq to hold LUT frequencies. For the 42688/42686 path the LUT includes values >127 (e.g. 258, 303), which overflow int8_t and can cause the function to pick the wrong AAF candidate.

Issue Context

This function selects the closest supported AAF cutoff frequency by comparing ABS(desiredFreq - aafConfigs[i].freq) against ABS(desiredFreq - selectedFreq). If selectedFreq wraps, the comparison becomes invalid.

Fix

  • Change selectedFreq to a wide signed type (e.g. int32_t or uint16_t, but prefer int32_t for safe signed subtraction).
  • Ensure the subtraction is performed in a sufficiently wide signed type, e.g.:
    • const int32_t desired = desiredFreq;
    • compare ABS(desired - (int32_t)aafConfigs[i].freq)

Fix Focus Areas

  • src/main/drivers/accgyro/accgyro_icm42605.c[415-443]


                     PR 11677 (2026-07-01)                    
[correctness] LIMIT rate can rebound
LIMIT rate can rebound The docs say `fw_autotune_rate_adjustment=LIMIT` "only ever lowers" rates, but the implementation can also increase a previously-lowered rate again (while still capping it at the initial rate). Users may therefore still observe `roll_rate`/`pitch_rate`/`yaw_rate` increases during a LIMIT autotune flight, contradicting the doc.

Issue description

docs/Autotune - fixedwing.md currently states that fw_autotune_rate_adjustment=LIMIT "only ever lowers" the rates. In the code, rate adjustment can step up or down by 10 dps based on measured capability; in LIMIT mode it is merely capped at the starting rate (and can rise back toward that cap).

Issue Context

This is documentation-only, but it can still mislead users who choose LIMIT expecting a strictly one-way decrease.

Fix Focus Areas

  • docs/Autotune - fixedwing.md[27-27]

Suggested wording example:

  • Replace "LIMIT only ever lowers them" with something like "LIMIT will not increase rates above the starting values (but may raise a previously-lowered rate back up to that cap)."


                     PR 11675 (2026-07-01)                    
[reliability] PWM beeper init unchecked
PWM beeper init unchecked In the runtime-assigned beeper path, beeperInit() sets pwmMode=true and returns without verifying that beeperPwmInit() succeeded; if PWM allocation fails, pwmWriteBeeper() becomes a no-op and the compile-time beeper fallback is skipped, disabling beeps silently. This creates a brittle failure mode when timer channel allocation fails (e.g., no TCH available).

Issue description

The runtime OUTPUT_MODE_BEEPER path unconditionally commits to PWM mode (beeperConfigMutable()->pwmMode = true) and returns after calling beeperPwmInit(), but beeperPwmInit() can fail and leave the beeper backend uninitialized. In that case pwmWriteBeeper() no-ops and there is no fallback.

Issue Context

  • beeperPwmInit() can return early when timerGetTCH() fails.
  • pwmWriteBeeper() returns immediately when beeperPwm == NULL.

Fix Focus Areas

  • src/main/drivers/sound_beeper.c[81-91]

Implementation direction

  • Make beeperPwmInit() report success (e.g., return bool), or add a small getter like bool beeperPwmIsInitialized(void).
  • In the runtime assignment path:
    • only set pwmMode=true and return if initialization succeeded;
    • otherwise continue into the existing compile-time beeper initialization path (or log an error and keep beeper in GPIO mode if possible).


                     PR 11536 (2026-05-04)                    
[correctness] Stale RPM still forwarded
Stale RPM still forwarded crsfRpm() considers any ESC telemetry with dataAge < ESC_DATA_INVALID (255) valid, so after an ESC stops responding CRSF can continue forwarding last-seen RPM until dataAge saturates at 255 (potentially tens of seconds+ with multi-motor polling). This is inconsistent with other telemetry/UI paths that treat ESC data stale after ESC_DATA_MAX_AGE (10), and can keep showing incorrect RPM long after disarm/ESC dropout.

Issue description

crsfRpm() currently forwards RPM for any ESC slot with dataAge < ESC_DATA_INVALID (255). That suppresses never-initialized slots, but still forwards very stale last-seen values until dataAge saturates at 255, which can take a long time with round-robin polling.

Issue Context

Other telemetry/UI paths treat ESC telemetry as valid only when dataAge <= ESC_DATA_MAX_AGE.

Fix Focus Areas

  • src/main/telemetry/crsf.c[335-358]

Suggested change

Gate RPM with escState->dataAge <= ESC_DATA_MAX_AGE (or an equivalent freshness threshold) instead of < ESC_DATA_INVALID.



                     PR 11389 (2026-03-02)                    
[reliability] Headers rely on transitive CONCAT4
Headers rely on transitive CONCAT4 The new bus_spi_stm32{h7,f7}xx.h headers use CONCAT4 but do not include the header that defines it, relying on current include order/transitive includes from other headers. This is a latent build fragility: a future refactor or reuse of these headers elsewhere can cause compile failures (e.g., CONCAT4 undefined).

Issue description

The new SPI AF lookup table headers use CONCAT4(...) but do not include the header that defines it (common/utils.h). They currently compile only due to transitive includes (via drivers/io.hio_def.hcommon/utils.h) and therefore are fragile to include-order changes or reuse.

Issue Context

This is a latent build fragility / maintainability issue: it may not break today, but it can break later during refactors or if another file includes these headers without including common/utils.h first.

Fix Focus Areas

  • src/main/drivers/bus_spi_stm32h7xx.h[32-38]
  • src/main/drivers/bus_spi_stm32f7xx.h[36-42]


                     PR 11381 (2026-03-01)                    
[correctness] Release tag not pinned
Release tag not pinned The workflow states the tag will be recreated pointing to the new commit, but `gh release create` does not explicitly target `workflow_run.head_sha`. Without an explicit target (and with no checkout), the release tag can be created against an unintended commit, breaking traceability between the PR commit and the published binaries.

Issue description

gh release create is invoked without explicitly targeting the triggering PR commit (github.event.workflow_run.head_sha), despite comments indicating the tag should be recreated to point at the new commit.

Issue Context

This workflow is publishing firmware for a specific PR commit; the tag should deterministically reference that commit SHA.

Fix Focus Areas

  • .github/workflows/pr-test-builds.yml[65-97]

[reliability] Missing issues permission
Missing issues permission The job only grants `pull-requests: write`, but the github-script step uses `github.rest.issues.*` endpoints to list/update/create PR comments. This can fail with authorization errors and prevent posting/updating the PR download link.

Issue description

The workflow uses the Issues API to manage PR comments but does not grant issues: write in the job permissions.

Issue Context

actions/github-script uses GITHUB_TOKEN, whose scopes are governed by the workflow permissions: block.

Fix Focus Areas

  • .github/workflows/pr-test-builds.yml[25-28]

[reliability] `parseInt` result not validated
`parseInt` result not validated The PR comment step uses `parseInt(process.env.PR_NUMBER)` without checking for `NaN` or enforcing base-10 parsing, which can lead to non-deterministic behavior if the env var is missing/malformed. This violates the requirement to validate external inputs and handle invalid values deterministically.

Issue description

The workflow parses PR_NUMBER from an environment variable using parseInt(...) and then uses it without checking for NaN (and without specifying radix 10). If the env var is missing or malformed, this can produce non-deterministic behavior (e.g., NaN in URLs / API params) instead of a clear, deterministic failure.

Issue Context

This job runs with elevated permissions (pull-requests: write) and should validate external inputs (including env vars derived from artifacts/outputs) before use.

Fix Focus Areas

  • .github/workflows/pr-test-builds.yml[107-110] բավ

[reliability] Concurrency key collisions
Concurrency key collisions Concurrency is keyed only by `head_branch`, so two different fork PRs using the same branch name can cancel each other’s publish jobs. This can lead to missing/late PR test builds because runs are canceled unnecessarily.

Issue description

Concurrency grouping uses only head_branch, which can collide across forks and cause unrelated runs to cancel each other.

Issue Context

This job publishes per-PR releases; cancellations should be limited to the same PR, not other forks with similarly named branches.

Fix Focus Areas

  • .github/workflows/pr-test-builds.yml[20-24]


                     PR 11321 (2026-02-09)                    
  • [possible issue] In `SD_StartBlockTransfert`, check if the DMA disable loop timed out. If it did, set an error and return to avoid reconfiguring an active DMA stream, which could cause unpredictable behavior.

  • [possible issue] In `SD_HighSpeed`, if the `swTimeout` is reached while waiting for SDIO status, return an error like `SD_TIMEOUT` instead of just breaking the loop to prevent processing incomplete data. [possible issue, importance: 7]
    New proposed code:

  •                      PR 11315 (2026-02-08)                    
  • [learned best practice] Make the replacement guidance exact per command (get vs set) to avoid ambiguity and divergence with docs/clients; reference only the corresponding replacement for each define.

  •                      PR 11313 (2026-02-06)                    
    [correctness] `dsdlc_generated` code committed
    `dsdlc_generated` code committed This PR adds `dsdlc_generated` DroneCAN DSDL outputs directly to the repo, which are generated artifacts and can make builds non-reproducible and the repo noisy. These files should be generated into the build directory (or updated via an explicit opt-in step) and excluded from normal source tracking.

    Issue description

    The PR commits DSDL-generated DroneCAN sources/headers under dsdlc_generated, which are generated artifacts.

    Issue Context

    Generated artifacts should be produced as part of the build (or via an explicit opt-in update command) and not be committed as normal source to keep the repository clean and reproducible.

    Fix Focus Areas

    • src/main/drivers/dronecan/dsdlc_generated/src/uavcan.equipment.air_data.Sideslip.c[1-8]
    • cmake/main.cmake[2-12]

    [correctness] `canardSTM32ComputeTimings` unchecked
    `canardSTM32ComputeTimings` unchecked `canardSTM32ComputeTimings()` returns `bool` but its result is ignored and `out_timings` is used unconditionally to configure the peripheral. If timing computation fails, CAN may be initialized with invalid/uninitialized timing values without any error propagation.

    Issue description

    CAN timing computation failure is ignored, potentially configuring hardware with invalid values.

    Issue Context

    The timing helper explicitly returns false for invalid/unsatisfied configurations; initialization should not proceed on failure.

    Fix Focus Areas

    • src/main/drivers/dronecan/libcanard/canard_stm32h7xx_driver.c[162-168]

    [correctness] `canardSTM32CAN1_Init()` return ignored
    `canardSTM32CAN1_Init()` return ignored `canardSTM32CAN1_Init()` returns a status code but `dronecanInit()` ignores it and continues initialization. This can leave DroneCAN partially initialized and failing silently at runtime.

    Issue description

    DroneCAN initialization ignores CAN peripheral init failures.

    Issue Context

    Proceeding after failed CAN init can cause confusing runtime behavior and make debugging difficult.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[404-440]

    [correctness] dronecan.h missing deps
    dronecan.h missing deps `dronecan.h` uses `PG_DECLARE` but doesn’t include `config/parameter_group.h` (and lacks a header guard), making builds depend on include order. `fc_tasks.c` includes `dronecan.h` before any header that defines `PG_DECLARE`, which can cause compile failures.

    Issue description

    src/main/drivers/dronecan/dronecan.h is not self-contained: it uses PG_DECLARE but does not include the header that defines it (config/parameter_group.h). This creates brittle include-order dependencies and can break compilation in files that include dronecan.h early (e.g. fc_tasks.c).

    Issue Context

    PG_DECLARE is defined in src/main/config/parameter_group.h. Some compilation units include dronecan.h before any header that brings in parameter_group.h.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.h[1-19]
    • src/main/fc/fc_tasks.c[18-43]
    • src/main/config/parameter_group.h[104-110]

    [correctness] PG_ID_LAST excludes DroneCAN
    PG_ID_LAST excludes DroneCAN `PG_DRONECAN_CONFIG` is defined but `PG_INAV_END`/`PG_ID_LAST` is not updated, so DroneCAN config is outside the enumerated PG range. MSP parameter group enumeration uses `PG_ID_LAST`, so tools won’t discover DroneCAN parameters.

    Issue description

    PG_DRONECAN_CONFIG is outside the [PG_ID_FIRST..PG_ID_LAST] range because PG_INAV_END wasn’t updated. This prevents MSP parameter-group enumeration from reporting DroneCAN config.

    Issue Context

    MSP uses PG_ID_FIRST/PG_ID_LAST to enumerate parameter groups.

    Fix Focus Areas

    • src/main/config/parameter_group_ids.h[132-150]
    • src/main/fc/fc_msp.c[3899-3919]

    [correctness] TX queue popped on failure
    TX queue popped on failure `processCanardTxQueue()` always pops the libcanard TX queue even when `canardSTM32Transmit()` returns 0 (not sent, e.g. TX FIFO full). This will silently drop DroneCAN frames under load.

    Issue description

    processCanardTxQueue() drops frames by popping the TX queue even when the hardware transmit reports “not sent yet” (return 0).

    Issue Context

    On STM32H7 the transmit function returns 0 when HAL_FDCAN_AddMessageToTxFifoQ fails (e.g. TX FIFO full), which should be retried.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[358-375]
    • src/main/drivers/dronecan/libcanard/canard_stm32h7xx_driver.c[83-128]

    [correctness] F7 transmit always succeeds
    F7 transmit always succeeds On STM32F7, `canardSTM32Transmit()` returns 1 even when `HAL_CAN_Transmit()` fails, masking errors and causing upper layers to believe the frame was sent.

    Issue description

    STM32F7 canardSTM32Transmit() reports success even on HAL transmit failure, hiding errors and causing silent packet loss.

    Issue Context

    Callers use the return value to decide whether to keep or drop frames.

    Fix Focus Areas

    • src/main/drivers/dronecan/libcanard/canard_stm32f7xx_driver.c[127-170]
    • src/main/drivers/dronecan/dronecan.c[358-375]

    [correctness] RX loop skips messages
    RX loop skips messages The DroneCAN RX processing loop decrements `numMessagesToProcess` twice (in the `for` header and inside the body), which will process only roughly half the queued frames and can starve RX under load.

    Issue description

    The RX loop decrements its loop counter twice, causing it to skip frames.

    Issue Context

    canardSTM32GetRxFifoFillLevel() is used to determine how many frames to process.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[462-477]

    [correctness] F7 unique ID collision
    F7 unique ID collision STM32F7 `canardSTM32GetUniqueID()` uses `HAL_GetDEVID()` (device type ID), not the MCU unique ID, so multiple F7 devices will report identical IDs on the bus. This breaks NodeInfo uniqueness assumptions and can break DNA/identification workflows.

    Issue description

    STM32F7 unique ID generation is not unique, causing collisions between devices.

    Issue Context

    DroneCAN node identification expects a stable per-device unique ID.

    Fix Focus Areas

    • src/main/drivers/dronecan/libcanard/canard_stm32f7xx_driver.c[430-441]
    • src/main/drivers/dronecan/libcanard/canard_stm32h7xx_driver.c[388-395]

    [correctness] Decode failures return silently
    Decode failures return silently Multiple DroneCAN handlers drop messages by returning immediately when `*_decode()` fails without logging or any other error signal. This creates silent failures that are hard to diagnose in-field.

    Issue description

    Decode failures are silently ignored, making it difficult to detect malformed frames or schema mismatches.

    Issue Context

    This is a new external-input path (CAN bus) where malformed payloads are expected; diagnostics should be available without flooding logs.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[47-129]

    [security] GNSS logs expose coordinates
    GNSS logs expose coordinates The new debug logging prints raw GPS latitude/longitude values, which can be sensitive location data, and uses unstructured log strings. This may violate secure logging requirements and makes automated log analysis harder.

    Issue description

    Logs include raw GNSS coordinates and unstructured strings.

    Issue Context

    Location data can be sensitive; logs should avoid detailed location output by default and be machine-parseable where possible.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[99-135]

    [correctness] 377-byte stack buffer used
    377-byte stack buffer used `handle_GetNodeInfo()` and `send_NodeStatus()` allocate `uint8_t buffer[UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE]` on the stack, where the max is 377 bytes. In embedded/IO paths this increases stack pressure and risk of overflow, especially if called in constrained task contexts.

    Issue description

    Large stack buffers are allocated in DroneCAN handler/sender paths, increasing stack pressure.

    Issue Context

    Embedded systems often have small stacks per task/ISR; using static/shared buffers or preallocated pools is safer.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[158-205]
    • src/main/drivers/dronecan/dsdlc_generated/include/uavcan.protocol.GetNodeInfo_res.h[10-16]

    [performance] 1Hz tasks spam post-boot
    1Hz tasks spam post-boot `next_1hz_service_at` starts at 0 and is updated using `+= 1000000`, so for approximately the first second after boot (or after a long pause), `process1HzTasks()` will run on every DroneCAN scheduler tick rather than at 1Hz.

    Issue description

    The 1Hz scheduler gate runs too frequently right after boot because next_1hz_service_at starts at zero and is advanced via +=.

    Issue Context

    This causes NodeStatus transmission and stale-transfer cleanup to run far more often than intended immediately after startup.

    Fix Focus Areas

    • src/main/drivers/dronecan/dronecan.c[445-483]


                         PR 11311 (2026-02-03)                    
  • [possible issue] In `teraRangerUpdate`, move the `busWrite()` call that triggers a new measurement to before any early `return` statements. This ensures the sensor is always re-triggered after a read attempt, preventing it from getting stuck.

  • [learned best practice] Initialize the measurement field to a fail-safe sentinel (e.g., `RANGEFINDER_NO_NEW_DATA`) so startup/first-read states don't appear as a valid `0cm` reading.

  •                      PR 11309 (2026-02-03)                    
  • [learned best practice] Keep the bold marker on the same line to avoid broken markdown rendering and to prevent parsers from misreading the revision string.

  •                      PR 11308 (2026-02-02)                    
  • [possible issue] Increment `warningFlagID` after the new battery voltage warning check to assign it a unique ID and prevent conflicts with other warnings. [possible issue, importance: 9]
    New proposed code:

  •                      PR 11283 (2026-01-23)                    
  • [general] Improve the `sed` command's robustness by using `

  • [learned best practice] Validate `"$#"` before reading `$1` and print a short usage message on error so failures are deterministic and user-friendly.

  • [learned best practice] Avoid mutating `cmake/pg_struct_sizes.db` in a POST_BUILD step; instead write an updated copy to a build output file (or gate updates behind an explicit `--update-db` flag) and print instructions to apply it.

  •                      PR 11279 (2026-01-23)                    
  • [possible issue] Resolve a pin conflict for `PC6` which is used for both `TIM8` (softserial) and `UART6_TX_PIN`. Comment out or remove the timer definition to avoid the conflict.

  • [general] Resolve a pin conflict by assigning the software-serial RX pin to a different pin than `PC6`, as `PC6` is already used for both software-serial TX and `UART6_TX`.

  • [possible issue] Add a check to ensure the index returned by `findSerialPortIndexByIdentifier` is valid (>= 0) before using it to access the `portConfigs` array to prevent potential crashes.

  •                      PR 11272 (2026-01-22)                    
  • [general] Avoid running the `check-pg-versions.sh` script twice by capturing its output in the first run and passing it to the subsequent step, improving workflow efficiency.

  • [general] Improve the detection of struct modifications by using a more robust `sed` and `grep` pipeline to filter out comments and whitespace, making the check less prone to false positives.

  • [general] Use a `while read` loop instead of a `for` loop to iterate over changed files, which correctly handles filenames that may contain spaces.

  •                      PR 11270 (2026-01-22)                    
  • [axis]`. [possible issue] Fix a copy-paste error in the velocity correction constraint. The `constrainf` function for `ctx.estVelCorr.v

  •                      PR 11268 (2026-01-22)                    
  • [general] Clarify the troubleshooting guide for "Surface mode not working" by distinguishing between `nav_max_terrain_follow_alt` (max operational altitude) and `inav_max_surface_altitude` (sensor's reliable range).

  •                      PR 11260 (2026-01-19)                    
  • [possible issue] In `impl_timerPWMSetDMACircular`, add a check after the DMA disable wait loop to handle the timeout case. If the timeout is reached, abort the DMA reconfiguration to prevent potential instability.

  • [possible issue] In `impl_timerPWMSetDMACircular` for `stdperiph`, add a polling loop to wait for the DMA stream's enable bit to clear after disabling it, before changing its configuration. This prevents a race condition.

  • [learned best practice] Only toggle motor DMA mode and force motor updates when a compatible digital motor protocol is actually in use (and motors exist), otherwise skip to avoid unintended side effects or no-op timing delays.

  • [possible issue] Fix the incorrect DMA timeout check by re-evaluating the stream status after the loop instead of checking if `timeout` is zero.

  • [i].pwmport->configured` before changing the dma mode to ensure the motor port is configured. [general] Add a check for `motors

  • [learned best practice] Match the HAL implementation by disabling the timer’s DMA request source before disabling/reconfiguring the DMA stream, then re-enable it afterward to prevent mid-transition DMA triggers.

  •                      PR 11194 (2025-12-21)                    
  • [general] Add a timeout or retry counter to the polling loops in `STORAGE_Read` to prevent the system from hanging if the SD card becomes unresponsive.

  •                      PR 11191 (2025-12-19)                    
  • [general] Replace the git commands for updating a fork with a more robust `git push` command that syncs the maintenance branch directly from upstream, avoiding the need for a local checkout.

  •                      PR 11189 (2025-12-18)                    
  • [possible issue] In `processCrsf`, check the return value of `crsfRpm` and only call `crsfFinalize` if data was successfully written to the buffer.

  • [possible issue] Fix the airspeed calculation in `crsfFrameAirSpeedSensor` by using floating-point division (e.g., `36.0f / 100.0f`) to prevent the expression from evaluating to zero.

  • [possible issue] In `processCrsf`, make the call to `crsfFinalize` conditional on `crsfTemperature` actually writing data to avoid sending empty temperature frames.

  • [learned best practice] Before writing variable-length frames, validate the computed payload/frame length against `CRSF_FRAME_SIZE_MAX`/`CRSF_PAYLOAD_SIZE_MAX` (and remaining `sbuf` capacity) and clamp counts or abort if it won’t fit.

  •                      PR 11187 (2025-12-18)                    
  • [possible issue] Correct the current limit calculation formula. The formula should be `battery_capacity_mAh × C_rating / 100` to yield a result in deci-amps (dA), not amps.

  •                      PR 11182 (2025-12-15)                    
  • [possible issue] Change the beeper's timer from `TIM1, CH1` to `TIM1, CH2` to resolve a resource conflict with the LED strip, which uses the same timer channel.

  • [possible issue] Correct the duplicated motor output indices in the `timerHardware` array for outputs S7 through S12 to ensure they are unique and sequential.

  • [learned best practice] Remove or enable the second IMU registration to match the dual-IMU macros in target.h, avoiding dead or misleading configuration.

  •                      PR 11157 (2025-12-03)                    
  • [possible issue] Add a definition for the specific magnetometer hardware driver (e.g., `USE_MAG_QMC5883L`) to ensure the device is properly initialized, as simply defining `USE_MAG` is insufficient.

  •                      PR 11152 (2025-11-30)                    
  • [general] Add a check to the firmware renaming script to ensure `.hex` files exist before attempting to loop through and rename them, preventing potential errors.

  • [general] Replace the risky `rm -rf */` command with a safer `find` command to delete only the empty subdirectories, preventing accidental deletion of other directories.

  • [learned best practice] Remove brittle line-number references and add commands to programmatically verify/update versions to prevent drift.

  •                      PR 11148 (2025-11-30)                    
  • [learned best practice] Await the API call and wrap in try/catch to fail the step on error and aid troubleshooting.

  • ⚠️ **GitHub.com Fallback** ⚠️