Skip to content

Fix the Parameter Group Version Check so it can run on pull requests (backport to release/9.1) - #11951

Open
sensei-hacker wants to merge 8 commits into
iNavFlight:release/9.1from
sensei-hacker:pg-version-check-release91
Open

sensei-hacker wants to merge 8 commits into
iNavFlight:release/9.1from
sensei-hacker:pg-version-check-release91

Conversation

@sensei-hacker

@sensei-hacker sensei-hacker commented Sep 14, 2026

Copy link
Copy Markdown
Member

Summary

Backport of #11885 onto release/9.1 - cherry-picks its 6 commits fixing the "Parameter Group Version Check" CI job, since this is a CI/tooling fix rather than a breaking change and should land as early as possible so it flows forward into maintenance-10.x and master naturally (same rationale as #11946 backporting #11875).

The original bug: check-pg-versions.sh declared local companion outside a function, so under set -e the script aborted with local: can only be used in a function before checking anything - breaking this CI job on every PR that touches a .c/.h file under src/. #11885 replaces the bash implementation with a Python checker (check-pg-versions.py) that also fixes several correctness gaps: cross-file struct/registration matching, #if/#ifdef conditional-variant awareness, and comparing against git merge-base instead of the branch tip so unrelated base-branch commits landing after the PR opened aren't misattributed as PR changes.

One additional commit was needed to make this actually work correctly, found by the mandatory inav-code-review pass (see below) - not a release/9.1-specific divergence, but a real bug in the original 6 commits:

  • Fix PG checker output-format mismatch and non-UTF-8 file handling - two commits within Fix the Parameter Group Version Check so it can run on pull requests #11885's own chain drifted apart: d42addb88f added a workflow guard requiring '### ' in the checker's stdout to treat a detection (exit 1) as normal, but 5efd794951 rewrote the checker in Python, which prints "PG version issue: ..." with no ### anywhere. As shipped, the workflow would hard-fail (exit 2, raw stdout dump, no PR comment) on every genuine detection - the one case this tooling exists for. Fixed both the bash guard and the PR-comment JS's output filter to match the actual output format, and decoded git subprocess output with errors='replace' so a stray non-ASCII byte in an unrelated comment can't raise an uncaught UnicodeDecodeError and hard-fail the check with a message that doesn't name the file. Added a regression fixture that runs the workflow's own guard logic (read live from the YAML, not duplicated) against a real detected issue, so the two can't silently diverge again.

release/9.1's own copy of pg-version-check.yml (like maintenance-10.x's, prior to #11885) previously only triggered on PRs targeting maintenance-9.x/maintenance-10.x, so bugfixes landing directly on release/9.1 that touch a PG struct got no automated version-bump check at all. Added release/9.1 to the trigger's branch list (and updated .github/workflows/README.md's documented scope to match), so this backport now also gives release/9.1 its own PR coverage, not just a clean forward-merge path.

Changes

  • Rewrite .github/scripts/check-pg-versions.sh as a thin wrapper around a new .github/scripts/check-pg-versions.py, which fixes the local-outside-a-function crash and adds cross-file/conditional-variant PG version checking
  • Add .github/scripts/test-check-pg-versions.py with 17 fixture cases (16 from the original PR plus 1 new regression fixture added in this backport)
  • Update .github/workflows/pg-version-check.yml and .github/workflows/README.md accordingly
  • Fix an output-format mismatch between the workflow's bash guard/PR-comment JS and the new Python checker's actual output (see above)
  • Add release/9.1 to the check's trigger branches, so it now runs on release/9.1 PRs too, not just maintenance-9.x/maintenance-10.x

Testing

  • Ran the full 17-fixture test suite (test-check-pg-versions.py) on this branch: all pass
  • Reproduced the original bug directly against release/9.1's pre-fix check-pg-versions.sh: local: can only be used in a function
  • Simulated the actual GitHub Actions job end-to-end in a disposable clone (checkout, fetch base into origin/<base>, GITHUB_BASE_REF/GITHUB_HEAD_REF env, run check-pg-versions.sh) against a real change to src/main/fc/stats.h/stats.c (statsConfig_t): correctly flagged the missing version bump, and correctly passed once the version was bumped
  • Verified (and fixed) the workflow's own bash guard against the checker's real "issue found" output - the mandatory inav-code-review pass caught that this previously hard-failed on any real detection
  • Validated .github/workflows/pg-version-check.yml's YAML after adding release/9.1 to the trigger branches; re-ran the full 17-fixture suite (unaffected, since the trigger-branch list isn't exercised by the fixtures)

Code Review

Reviewed with the inav-code-review agent, which verified by direct execution (not just reading the diff) that the workflow's output-parsing guard and PR-comment JS filter were never updated to match the Python checker's output format, and that git subprocess output was decoded with strict UTF-8. Both fixed in this PR's additional commit above. The agent also flagged a MINOR gap (attribute-decorated struct declarations like typedef struct PG_PACKED { ... } aren't recognized by the struct-detection regex) which isn't hit by any currently-registered PG struct; left as-is per the agent's own assessment that this is a latent gap rather than an active issue, worth a doc note rather than a code change.

Related

Backport of #11885 for release/9.1 (original PR targets maintenance-10.x; a maintainer should confirm this is later reconciled/isn't otherwise superseded once both branches converge). Author of the original 6 commits is Raffi1202.

https://claude.ai/code/session_01YbtjVLXNSuAxcW81c1SuiY

Raphael Hunziker and others added 7 commits September 14, 2026 16:28
The check has failed on every pull request since it was added:

- check-pg-versions.sh declares `local companion` in the top-level loop
  that builds the file list. Outside a function bash rejects `local`,
  and with `set -e` the script aborts right there with "local: can only
  be used in a function", before any file is checked.
- The workflow then inlines the multi-line script output into the
  JavaScript source of the github-script step as a single-quoted string
  literal, which fails to parse ("SyntaxError: Invalid or unexpected
  token") and fails the job.

Drop the `local`, and hand the output to the script through an
environment variable instead of the source text.

Verified locally against a change that adds a field to
telemetryConfig_t: with the PG version bump the script reports "No PG
version issues detected" and exits 0, without the bump it reports the
struct, the unchanged version and the recommended increment and exits 1.
Reuse the CI fixes from iNavFlight#11885 and cover scalar, array and conditional registrations with regression fixtures.
The workflow README still described the shell implementation and its
local-testing snippet did not mention python3 or the regression
fixtures.
Two commits in this same PR chain drifted apart: d42addb added a
workflow guard requiring '### ' in the checker's stdout to treat exit
code 1 as a normal detection, but 5efd794 rewrote the checker in
Python, which prints "PG version issue: ..." with no '###' anywhere.
As a result the workflow hard-fails (exit 2, raw stdout dump) instead
of posting the intended PR comment on every genuine detection - the
one case this tooling exists for. Verified by running the workflow's
own guard logic against the checker's real "issue found" output before
and after this fix. Same fix applied to the PR comment step's JS
output filter, which keyed on the same stale '###' marker.

Also decode git subprocess output with errors='replace' instead of the
default strict UTF-8, since a single non-ASCII byte (e.g. a smart quote
in a comment) anywhere in a touched .c/.h file would otherwise raise an
uncaught UnicodeDecodeError and hard-fail the check with a message that
doesn't name the offending file.

Added a regression fixture that runs the workflow's actual guard logic
(read from the workflow YAML, not duplicated) against a real detected
issue, so the two can't silently diverge again.
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Backport robust parameter group version checks to release/9.1

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Replaces broken Bash logic with a Python checker for repository-wide parameter-group validation.
• Compares merge-base layouts and conditional variants to require appropriate version increments.
• Hardens workflow output handling and adds 17 regression fixtures plus usage documentation.
Diagram

graph TD
  A["Pull request"] --> B["CI workflow"] --> C["Regression tests"] --> D["Shell wrapper"] --> E["Python checker"] --> F[("Git history")] --> G["Output guard"] --> H["PR comment"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Repair the existing Bash checker
  • ➕ Produces a smaller backport with fewer runtime changes.
  • ➕ Avoids introducing Python as a checker dependency.
  • ➖ Retains fragile diff parsing and platform-specific shell behavior.
  • ➖ Makes cross-file and conditional registration analysis difficult to test and maintain.
  • ➖ Would not address the original checker's broader correctness gaps.
2. Use a compiler-backed C parser
  • ➕ Handles C syntax, attributes, macros, and nested declarations more accurately.
  • ➕ Could detect layout changes beyond the current regular-expression model.
  • ➖ Requires additional tooling and configuration-aware preprocessing in CI.
  • ➖ Firmware build variants make exhaustive preprocessing expensive and complex.
  • ➖ Adds disproportionate dependency and maintenance overhead for this targeted check.

Recommendation: Keep the standard-library Python implementation. It fixes the immediate CI failures while making merge-base, cross-file, and conditional checks testable without heavyweight dependencies; the compiler-backed option offers greater syntax coverage but is excessive for this backport.

Files changed (5) +325 / -231

Bug fix (1) +199 / -0
check-pg-versions.pyAdd repository-wide parameter group version checker +199/-0

Add repository-wide parameter group version checker

• Introduces a Python checker that compares changed struct layouts from the PR merge base and maps registrations across all C/H files. It evaluates preprocessor variants symbolically, validates version increases, replaces undecodable Git bytes safely, and distinguishes findings from checker failures.

.github/scripts/check-pg-versions.py

Refactor (1) +2 / -224
check-pg-versions.shReplace fragile Bash implementation with a Python launcher +2/-224

Replace fragile Bash implementation with a Python launcher

• Reduces the existing shell entry point to a strict wrapper that executes the new Python checker. This removes the top-level 'local' crash while preserving the workflow's established command and exit-code contract.

.github/scripts/check-pg-versions.sh

Tests (1) +94 / -0
test-check-pg-versions.pyAdd comprehensive parameter group checker fixtures +94/-0

Add comprehensive parameter group checker fixtures

• Adds 17 temporary-repository fixtures covering unchanged and bumped versions, arrays, cross-file registrations, conditional and compound variants, and advanced base branches. A workflow regression test executes the live YAML guard against a genuine finding to prevent output-format drift.

.github/scripts/test-check-pg-versions.py

Documentation (1) +13 / -5
README.mdDocument the revised checker behavior and test command +13/-5

Document the revised checker behavior and test command

• Explains repository-wide registration mapping, merge-base comparison, and condition-aware version validation. It also documents the Python requirement and local regression-suite command.

.github/workflows/README.md

Other (1) +17 / -2
pg-version-check.ymlTest the checker and safely process its output +17/-2

Test the checker and safely process its output

• Runs the workflow when checker files change and executes the regression suite before validation. It verifies expected issue output, treats malformed or failed runs as errors, passes multiline output through the environment, and filters comments using the Python checker's actual message prefix.

.github/workflows/pg-version-check.yml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Fork detections cannot post warnings 🐞 Bug ☼ Reliability
Description
The repaired output handoff now reaches github.rest.issues from a workflow triggered directly by
pull_request, whose token is read-only for fork-originated runs. When a fork changes a persisted
layout without a version bump, the create or update request reaches the catch handler and fails the
job without posting the diagnostic comment.
Code

.github/workflows/pg-version-check.yml[67]

+            const output = process.env.PG_CHECK_OUTPUT || '';
Evidence
The changed environment-based output handling makes the comment code executable, and that code
performs write operations and converts API failures into a failed step. The repository's existing
test-build workflow explicitly uses workflow_run rather than pull_request so privileged behavior
remains available for fork pull requests.

.github/workflows/pg-version-check.yml[3-22]
.github/workflows/pg-version-check.yml[57-67]
.github/workflows/pg-version-check.yml[109-143]
.github/workflows/pr-test-builds.yml[1-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The checker executes untrusted pull-request code and then tries to post its result using the same pull-request workflow token. Fork runs cannot obtain the required write access, so genuine detections fail during comment creation instead of publishing the warning.
## Fix Focus Areas
- .github/workflows/pg-version-check.yml[37-143]
- .github/workflows/pr-test-builds.yml[1-36]
## Recommended Fix
Keep checking in the unprivileged `pull_request` workflow, upload the checker result and validated pull-request number as artifacts, and add a privileged `workflow_run` consumer that downloads those artifacts and posts or updates the comment. Follow the repository's existing `pr-test-builds.yml` pattern and do not execute pull-request code in the privileged workflow.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Renamed settings structs skip checks 🐞 Bug ≡ Correctness
Description
check() builds changed with git diff --name-only, then loads both revisions using each
resulting path rather than preserving the old and new names of a rename. When a persisted struct's
file is renamed while its layout changes, the old definition never enters old_structs, so the
intersection at the comparison loop silently excludes it.
Code

.github/scripts/check-pg-versions.py[149]

+    changed = [path for path in git('diff', '--name-only', base + '..' + head).splitlines() if path.endswith(('.c', '.h'))]
Evidence
The checker only parses paths returned by the name-only diff and requires each alias in both maps
before checking it. controlConfig_t demonstrates that persisted structs can live in headers
separate from their registrations, so renaming and modifying such a header suppresses the intended
version validation.

.github/scripts/check-pg-versions.py[147-163]
src/main/fc/control_profile_config_struct.h[26-66]
src/main/fc/control_profile.c[34-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The checker loses the base-side definition when a parameter-group struct is modified as part of a detected file rename. Because comparison requires the alias in both structure maps, this allows a persisted layout change to pass without a version increase.
## Fix Focus Areas
- .github/scripts/check-pg-versions.py[147-163]
- .github/scripts/test-check-pg-versions.py[53-67]
## Recommended Fix
Collect both old and new paths for changed files, such as by disabling rename collapsing with `git diff --no-renames --name-only`, and load each path from whichever tree contains it. Add a fixture that renames a registered struct header, changes its layout without increasing the version, and expects exit code 1.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo


def check(base, head):
base = git('merge-base', base, head).strip()
changed = [path for path in git('diff', '--name-only', base + '..' + head).splitlines() if path.endswith(('.c', '.h'))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Renamed settings structs skip checks 🐞 Bug ≡ Correctness

check() builds changed with git diff --name-only, then loads both revisions using each
resulting path rather than preserving the old and new names of a rename. When a persisted struct's
file is renamed while its layout changes, the old definition never enters old_structs, so the
intersection at the comparison loop silently excludes it.
Agent Prompt
## Issue description
The checker loses the base-side definition when a parameter-group struct is modified as part of a detected file rename. Because comparison requires the alias in both structure maps, this allows a persisted layout change to pass without a version increase.

## Fix Focus Areas
- .github/scripts/check-pg-versions.py[147-163]
- .github/scripts/test-check-pg-versions.py[53-67]

## Recommended Fix
Collect both old and new paths for changed files, such as by disabling rename collapsing with `git diff --no-renames --name-only`, and load each path from whichever tree contains it. Add a fixture that renames a registered struct header, changes its layout without increasing the version, and expects exit code 1.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

script: |
// Use the captured output from the previous step
const output = '${{ steps.pg_check.outputs.output }}';
const output = process.env.PG_CHECK_OUTPUT || '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Fork detections cannot post warnings 🐞 Bug ☼ Reliability

The repaired output handoff now reaches github.rest.issues from a workflow triggered directly by
pull_request, whose token is read-only for fork-originated runs. When a fork changes a persisted
layout without a version bump, the create or update request reaches the catch handler and fails the
job without posting the diagnostic comment.
Agent Prompt
## Issue description
The checker executes untrusted pull-request code and then tries to post its result using the same pull-request workflow token. Fork runs cannot obtain the required write access, so genuine detections fail during comment creation instead of publishing the warning.

## Fix Focus Areas
- .github/workflows/pg-version-check.yml[37-143]
- .github/workflows/pr-test-builds.yml[1-36]

## Recommended Fix
Keep checking in the unprivileged `pull_request` workflow, upload the checker result and validated pull-request number as artifacts, and add a privileged `workflow_run` consumer that downloads those artifacts and posts or updates the comment. Follow the repository's existing `pr-test-builds.yml` pattern and do not execute pull-request code in the privileged workflow.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

The check previously only triggered on PRs targeting maintenance-9.x
and maintenance-10.x, so bugfixes landing directly on release/9.1 that
touch a PG struct got no automated version-bump check at all. Add
release/9.1 to the trigger's branch list and update the workflow
README's documented scope to match.
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

RAM / Flash usage vs. base commit 13485eb — commit 1721741

Target Flash Δ RAM Δ
MATEKF405 ±0 B (±0.00%) CCM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
MATEKF722 ±0 B (±0.00%) ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
TCM: ±0 B (±0.00%)
MATEKF765 ±0 B (±0.00%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: ±0 B (±0.00%)
MATEKH743 ±0 B (±0.00%) D2_RAM: ±0 B (±0.00%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Test firmware build ready — commit 1721741

Download firmware for PR #11951

245 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

@sensei-hacker sensei-hacker added this to the 9.1 milestone Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants