ci: only lint real steps, and find the real end of an expression - #124
Merged
Conversation
Two bugs in the linter #122 added, found by Qodo reviewing the port of it to OpenIPC/firmware and confirmed against this copy rather than taken on trust. walk() collected any mapping with a scalar `run` key, anywhere in the document. That is one key name away from linting things that are not shell: an action input, an env var or a matrix field called `run` holds arbitrary text, and feeding it to bash -n fails a workflow that is fine. On a fixture with one real step plus an `env: run:` and a `with: run:`, this copy collects three blocks. Steps now have to come out of a `steps:` sequence, which is the actual schema invariant and still not a hardcoded jobs.*.steps[*] path, so composite actions (`runs: steps:`) keep working. The expression substitution used `\$\{\{.*?\}\}`, which stops at the first `}}` even when it is inside a string literal. On x=${{ fromJSON('{"a": {"b": 1}}') }} this copy produces `x=__GHA_EXPR__') }}` and bash -n then reports an unbalanced quote -- a false failure on a valid block, in the direction that trains people to ignore the job. Replaced with a scanner that tracks GitHub's single-quoted strings, including the doubled '' escape, and returns the text untouched if an expression is never closed. Both are latent here: nothing in this repo has a `run` key outside a step or a `}}` inside an expression string, and the block count is unchanged at 23. They are fixed because the failure mode is a red job with nothing wrong with the tree, which is how a check stops being believed. Self-tests for both. The docstring described the behaviour this replaces, so it is corrected too, and the now-unused `re` import drops. Keeps this copy identical to firmware's apart from the incident paragraph and the block floor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoFix workflow shell lint step discovery and expression parsing
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two bugs in the linter #122 added. Found by Qodo reviewing the port of it to OpenIPC/firmware#2290, and confirmed against this copy rather than taken on trust.
Both are latent here — nothing in this repo currently has a
runkey outside a step or a}}inside an expression string, and the block count is unchanged at 23. They're fixed because the failure mode in both cases is a red job with nothing wrong with the tree, which is how a check stops being believed.1. Non-step
run:keys were lintedwalk()collected any mapping with a scalarrunkey, anywhere in the document. That's one key name away from linting things that aren't shell — an action input, an env var, or a matrix field calledrunholds arbitrary text. On this fixture:the current implementation collects
['<unnamed>', '<unnamed>', 'real']— theenvvalue and the action input both get parsed as bash, and both would fail the job.A step now has to come out of a
steps:sequence. I kept structural discovery rather than hardcodingjobs.*.steps[*](which the bot suggested), because thesteps:parent is the actual schema invariant and it keeps composite actions (runs: steps:) covered by the same rule.2. Expression end was found naively
The substitution used
\$\{\{.*?\}\}, which stops at the first}}even when it's inside a string literal:produces
x=__GHA_EXPR__') }}, andbash -nthen reportsunexpected EOF while looking for matching \'`. A false failure on a perfectly valid block.Replaced with a scanner that tracks GitHub's single-quoted string literals including the doubled
''escape, and returns the text untouched if an expression is never closed — a malformed workflow isn't this checker's business.Verification
master.yml:354 (Collect assets)with the runner's own messagereimport droppedSelects 0 devices, thanks to the classification in #122.
🤖 Generated with Claude Code