.pr_agent_auto_best_practices - iNavFlight/inav GitHub Wiki

Pattern 1: Check status-returning initialization, transmission, encoding, and API calls before proceeding or consuming their outputs. Preserve queued work on retryable failures and propagate or log terminal failures instead of leaving subsystems partially initialized or emitting empty frames.

Example code before:

initializePeripheral(&config);
transmitFrame(frame);
queuePop(queue);

Example code after:

if (!initializePeripheral(&config)) {
    return INIT_ERROR;
}
if (transmitFrame(frame) > 0) {
    queuePop(queue);
}
Relevant past accepted suggestions:
Suggestion 1: [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).

Suggestion 2: [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]

Suggestion 3: [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]

Suggestion 4: [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]

Suggestion 5: [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]

Suggestion 6:
  • [possible issue] In `processCrsf`, check the return value of `crsfRpm` and only call `crsfFinalize` if data was successfully written to the buffer.
    Suggestion 7:
  • [possible issue] In `processCrsf`, make the call to `crsfFinalize` conditional on `crsfTemperature` actually writing data to avoid sending empty temperature frames.
    Suggestion 8:
  • [learned best practice] Await the API call and wrap in try/catch to fail the step on error and aid troubleshooting.

  • Pattern 2: Bound hardware polling loops with a timeout and verify the hardware state after the loop before reconfiguring or processing data. Return an explicit timeout error when the expected state transition did not occur.

    Example code before:

    while (dmaIsEnabled(stream)) {
        timeout--;
    }
    configureDma(stream);
    

    Example code after:

    while (dmaIsEnabled(stream) && timeout > 0) {
        timeout--;
    }
    if (dmaIsEnabled(stream)) {
        return DMA_TIMEOUT;
    }
    configureDma(stream);
    
    Relevant past accepted suggestions:
    Suggestion 1:
  • [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.
    Suggestion 2:
  • [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:
    Suggestion 3:
  • [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.
    Suggestion 4:
  • [possible issue] Fix the incorrect DMA timeout check by re-evaluating the stream status after the loop instead of checking if `timeout` is zero.
    Suggestion 5:
  • [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.

  • Pattern 3: Validate externally derived payload lengths, parsed identifiers, and lookup indices before reading buffers or indexing arrays. Reject malformed input deterministically and ensure variable-length output fits both protocol limits and remaining buffer capacity.

    Example code before:

    int index = findIndex(identifier);
    uint16_t value = readU16(input);
    entries[index] = value;
    

    Example code after:

    if (inputSize < sizeof(uint16_t)) {
        return INPUT_ERROR;
    }
    int index = findIndex(identifier);
    if (index < 0 || index >= entryCount) {
        return INPUT_ERROR;
    }
    entries[index] = readU16(input);
    
    Relevant past accepted suggestions:
    Suggestion 1: [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] բավ

    Suggestion 2:

    Add payload size validation check

    Add a payload size check in the MSP_OSD_CUSTOM_POSITION handler to ensure the incoming data is at least 3 bytes before reading from the buffer.

    src/main/fc/fc_msp.c [2718-2731]

     case MSP_OSD_CUSTOM_POSITION: {
    +    if (dataSize < 3) {
    +        return MSP_RESULT_ERROR;
    +    }
         uint8_t item;
         sbufReadU8Safe(&item, src);
         if (item < OSD_ITEM_COUNT){ // item == addr
             osdEraseCustomItem(item);
             osdLayoutsConfigMutable()->item_pos[0][item] = sbufReadU16(src) | (1 << 13);
             osdDrawCustomItem(item);
         }
         else{
             return MSP_RESULT_ERROR;
         }
     
         break;
     }

    Suggestion 3:
  • [learned best practice] Validate `"$#"` before reading `$1` and print a short usage message on error so failures are deterministic and user-friendly.
    Suggestion 4:
  • [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.
    Suggestion 5:
  • [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.

  • Pattern 4: Keep generated build outputs and environment-specific files out of version control and avoid mutating tracked source files during normal builds. Generate artifacts in the build directory or require an explicit opt-in update command.

    Example code before:

    add_custom_command(TARGET firmware POST_BUILD
        COMMAND update_database ${CMAKE_SOURCE_DIR}/config.db)
    

    Example code after:

    add_custom_command(TARGET firmware POST_BUILD
        COMMAND update_database ${CMAKE_BINARY_DIR}/config.updated.db)
    # Apply config.updated.db to the source tree only via an explicit update command.
    
    Relevant past accepted suggestions:
    Suggestion 1: [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]

    Suggestion 2:

    Remove generated build file from repository

    Remove the generated CMake build file from the repository. It contains user-specific absolute paths that will cause build failures for other developers and should be added to .gitignore.

    src/src/main/target/AXISFLYINGF7PRO/CMakeFiles/AXISFLYINGF7PRO_for_bl.elf.dir/DependInfo.cmake [1-474]

    -# Consider dependencies only in project.
    -set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF)
    +# This file should be removed from the repository.
     
    -# The set of languages for which implicit dependencies are needed:
    -set(CMAKE_DEPENDS_LANGUAGES
    -  "ASM"
    -  )
    -# The set of files for implicit dependencies of each language:
    -set(CMAKE_DEPENDS_CHECK_ASM
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/lib/main/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal2.S" "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/src/src/main/target/AXISFLYINGF7PRO/CMakeFiles/AXISFLYINGF7PRO_for_bl.elf.dir/__/__/__/__/lib/main/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal2.S.obj"
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/src/main/startup/startup_stm32f722xx.s" "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/src/src/main/target/AXISFLYINGF7PRO/CMakeFiles/AXISFLYINGF7PRO_for_bl.elf.dir/__/__/startup/startup_stm32f722xx.s.obj"
    -  )
    -set(CMAKE_ASM_COMPILER_ID "GNU")
    -
    -# Preprocessor definitions for this target.
    -set(CMAKE_TARGET_DEFINITIONS_ASM
    -...
    -
    -# The include file search paths:
    -set(CMAKE_ASM_TARGET_INCLUDE_PATH
    -  "main/target/AXISFLYINGF7PRO"
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/lib/main/STM32F7/Drivers/STM32F7xx_HAL_Driver/Inc"
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/lib/main/STM32F7/Drivers/CMSIS/Device/ST/STM32F7xx/Include"
    -...
    -

    Suggestion 3:

    Remove generated file from version control

    Remove the generated Makefile.cmake file from version control. This file is environment-specific and should be ignored by adding the CMakeFiles directory to .gitignore.

    src/CMakeFiles/Makefile.cmake [1-9]

    -# CMAKE generated file: DO NOT EDIT!
    -# Generated by "Unix Makefiles" Generator, CMake Version 4.1
    +# This file should be removed from the pull request and repository.
     
    -# The generator used is:
    -set(CMAKE_DEPENDS_GENERATOR "Unix Makefiles")
    -
    -# The top level Makefile was generated from the following files:
    -set(CMAKE_MAKEFILE_DEPENDS
    -  "CMakeCache.txt"
    -...
    -

    Suggestion 4:
  • [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.

  • Pattern 5: Guard numeric formulas against zero denominators and use sufficiently wide or floating-point intermediate types before division, subtraction, or magnitude calculations. Convert to the narrower destination type only after the calculation and any required range checks.

    Example code before:

    int8_t selected = table[i].frequency;
    float ratio = integerScale / integerDivisor;
    result = value / reference;
    

    Example code after:

    int32_t selected = (int32_t)table[i].frequency;
    float ratio = (float)integerScale / (float)integerDivisor;
    if (reference <= 0.0f) {
        return DEFAULT_RESULT;
    }
    result = value / reference;
    
    Relevant past accepted suggestions:
    Suggestion 1: [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]

    Suggestion 2:

    Avoid premature type casting to float

    In osdGet3DSpeed, declare vert_speed and hor_speed as float to prevent loss of precision from premature casting before the Pythagorean calculation.

    src/main/io/osd_common.c [202-207]

     int16_t osdGet3DSpeed(void)
     {
    -    int16_t vert_speed = getEstimatedActualVelocity(Z);
    -    int16_t hor_speed = gpsSol.groundSpeed;
    +    float vert_speed = getEstimatedActualVelocity(Z);
    +    float hor_speed = gpsSol.groundSpeed;
         return (int16_t)calc_length_pythagorean_2D(hor_speed, vert_speed);
     }

    Suggestion 3:

    Prevent division by zero during normalization

    Add a check to ensure vCoGlocal has a non-zero magnitude before normalization to prevent a potential division-by-zero error.

    src/main/flight/imu.c [472-482]

    -if (vectorNormSquared(&vHeadingEF) > 0.01f) {
    +if (vectorNormSquared(&vHeadingEF) > 0.01f && vectorNormSquared(&vCoGlocal) > 0.01f) {
         // Normalize to unit vector
         vectorNormalize(&vHeadingEF, &vHeadingEF);
         vectorNormalize(&vCoGlocal, &vCoGlocal);
     
         // error is cross product between reference heading and estimated heading (calculated in EF)
         vectorCrossProduct(&vCoGErr, &vCoGlocal, &vHeadingEF);
     
         // Rotate error back into body frame
         quaternionRotateVector(&vCoGErr, &vCoGErr, &orientation);
     }

    Suggestion 4:

    Prevent division-by-zero in TPA calculation

    Add a check to prevent division-by-zero when referenceAirspeed is zero in the tpaThrottle calculation, falling back to the standard throttle value if necessary.

    src/main/flight/pid.c [501-502]

     const float referenceAirspeed = pidProfile()->fixedWingReferenceAirspeed; // in cm/s
    -tpaThrottle = currentControlRateProfile->throttle.pa_breakpoint + (uint16_t)((airspeed - referenceAirspeed) / referenceAirspeed * (currentControlRateProfile->throttle.pa_breakpoint - getThrottleIdleValue()));
    +if (referenceAirspeed > 0) {
    +    tpaThrottle = currentControlRateProfile->throttle.pa_breakpoint + (uint16_t)((airspeed - referenceAirspeed) / referenceAirspeed * (currentControlRateProfile->throttle.pa_breakpoint - getThrottleIdleValue()));
    +} else {
    +    // Fallback to regular throttle if reference airspeed is not configured
    +    tpaThrottle = rcCommand[THROTTLE];
    +}

    Suggestion 5:
  • [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.

  • [Auto-generated best practices - 2026-08-04]

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