Skip to content

ci: lint the scripts and workflows on every pull request - #376

Merged
Reefact merged 2 commits into
mainfrom
claude/sonar-points-restants-12zlnr
Jul 29, 2026
Merged

ci: lint the scripts and workflows on every pull request#376
Reefact merged 2 commits into
mainfrom
claude/sonar-points-restants-12zlnr

Conversation

@Reefact

@Reefact Reefact commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Brings static analysis of the shell scripts and workflow definitions forward to the pull request, where every other check in this repository already lives. Until now nothing in CI read them: the only analysis was the sonar scan, which reports after the merge and enforces nothing — its job goes green as soon as the upload succeeds, whatever the Quality Gate says.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation
  • Build / CI / tooling

Changes

New lint workflow

  • shellcheck over every *.sh (14 scripts), actionlint over .github/workflows (18 files), on push to main, on every PR, and on dispatch.
  • Both run on our own runners. shellcheck ships preinstalled on the runner image, so no third-party action enters the supply chain; actionlint is a release tarball pinned by version and verified by SHA-256, for the same reason OpenSSF Scorecard's Pinned-Dependencies check exists.
  • The job declares its own contents: read instead of inheriting a workflow-level grant — githubactions:S8264 applied to the workflow that now guards against it.
  • The bar is zero findings, info included. A lower bar would let info accumulate exactly the way the Sonar report did, which is the problem this exists to prevent.

Making the tree clean at that bar — 9 findings, three causes

  • SC1091 ×2 was real. . "$(dirname "$0")/../trains.sh" is a path shellcheck cannot resolve statically, so it was skipping the sourced file entirely: tools/trains.sh, the single source of truth for the release trains, went unchecked in both scripts that read it. A # shellcheck source= directive fixes the analysis, not just the message.
  • SC2016 ×5 — false positive. Reads the Markdown backticks in a printf format as command substitution; every one of these formats emits a step summary or a PR comment.
  • SC2317 ×2 — false positive. Calls the hook's two rule functions unreachable, because they are reached through the "rule_${rule}" dispatch a static reader cannot follow. The rule's own message says to ignore it when the call is indirect.
  • Each false positive is annotated where it fires, with its reason. A .shellcheckrc would have silenced all seven in one line and blinded both rules everywhere else, including where they are right.

Documentation

  • lint.en.md / lint.fr.md in the workflow reference, plus a row in both README tables.

Testing

  • dotnet build FirstClassErrors.sln
  • dotnet test FirstClassErrors.sln
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests)

Zero warnings, 2092 tests passing. No C# changes in this PR, so the .NET suite is a regression check rather than the relevant one.

What actually verifies this change was run locally, using the exact commands the workflow will run:

  • shellcheck over all 14 scripts — exit 0, clean.
  • actionlint over all 18 workflows, including the new lint.yml itself — clean.
  • The SHA-256 verification step against the pinned tarball — OK.
  • Both Claude hooks still parse and run (history-hygiene exits 0).

Documentation

  • Public API / error documentation updated
  • README / doc/ updated
  • French translation (doc/handwritten/for-users/README.fr.md) updated if user-facing behavior changed
  • No documentation change required

Workflow reference updated in English and French, including an explicit statement of what this does not cover (see below). README.fr.md under for-users is untouched — no user-facing behaviour changes.

Architecture decisions

  • No architectural decision in this pull request
  • New decision recorded — ADR drafted as Proposed: ADR-____
  • Supersedes an existing ADR — successor proposed, status not flipped: ADR-____
  • ⚠️ Conflicts with an existing ADR — flagged for the maintainer: ADR-____

This PR is one step of a larger move — what blocks a merge runs on our runners; the SaaS informs, it does not guard — whose remaining steps are SonarAnalyzer.CSharp in the build and de-requiring the sonar check. I would rather draft that ADR once with the whole sequence than record a fragment of it here, but @Reefact should judge: say the word and I will propose it now.

Notes for the reviewer

actionlint audits correctness, not security posture. It does not flag over-broad permissions, spoofable actor checks or dangerous triggers — the class that produced this repository's two VULNERABILITY findings. This is stated in the workflow comment and in the reference doc so nobody mistakes this workflow for that coverage.

The tool that does cover it, zizmor, was run locally against the 18 workflows and reports 77 findings, 2 of them HIGH:

  • dependabot-autofix.yml:32workflow_run trigger, a known privilege-escalation vector.
  • dependabot-automerge.yml:30if: github.actor == 'dependabot[bot]', spoofable.

Plus 23 artipacked (medium, checkout without persist-credentials: false) and 10 template-injection (informational). Deliberately not in this PR: 77 findings, two of them on the Dependabot auto-merge path, deserve their own look rather than being buried here.

Related issues

None.


Generated by Claude Code

claude added 2 commits July 29, 2026 17:14
Nine findings across the shell scripts and the `run:` blocks, reducing to three
causes. None had ever been reported by anything the repository runs: shellcheck
and actionlint are not installed here, and SonarQube's shell analysis arrives
after the merge.

SC1091, twice, was real. `. "$(dirname "$0")/../trains.sh"` is a path shellcheck
cannot resolve statically, so it was skipping the sourced file entirely — the
single source of truth for the release trains went unchecked in both scripts
that read it. A `# shellcheck source=` directive fixes the analysis, not just the
message.

The other seven are false positives, each annotated where it fires rather than
disabled repository-wide. SC2016 (five) reads the Markdown backticks in a printf
format as command substitution; every one of these formats emits a step summary
or a pull-request comment. SC2317 (two) calls the hook's two rule functions
unreachable, because they are reached through the `"rule_${rule}"` dispatch a
static reader cannot follow — the rule's own message says to ignore it when the
call is indirect.

A `.shellcheckrc` would have silenced all seven in one line and blinded both
rules everywhere else, including where they are right.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bxVrnCNsXW3RvwmLc9Kvy
Every analysis in this repository runs inside a build — the Roslyn analyzers,
the explicit-type rule ADR-0055 restated in .editorconfig, the warning ratchet —
so a contributor meets it while writing the code. The shell scripts and the
workflow files had no such moment. The only thing reading them was the sonar
scan, which reports after the merge and enforces nothing: its job goes green as
soon as the analysis uploads, whatever the Quality Gate says. Two findings typed
VULNERABILITY reached main through that gap, and 21 shell findings accumulated
behind it.

The new `lint` workflow runs shellcheck over every *.sh and actionlint over
.github/workflows on every pull request. Both run on our own runners, so the
signal arrives before the merge and survives a SonarQube outage — which the
current arrangement does not: the sonar check is required and calls the service,
so an outage blocks merging while a red Quality Gate does not.

shellcheck is preinstalled on the runner image, so no third-party action enters
the supply chain. actionlint is a pinned release tarball verified by SHA-256, for
the same reason Scorecard's Pinned-Dependencies check exists. The job declares
its own `contents: read` rather than inheriting a workflow-level grant — the
S8264 lesson applied to the workflow that now guards against it.

The bar is zero findings, `info` included, and the tree is clean at that bar.
A lower bar would let `info` accumulate exactly the way the Sonar report did.

Documented in the workflow reference, English and French, including what this
does NOT cover: actionlint audits correctness, not security posture, so the class
that produced the two VULNERABILITY findings needs a dedicated auditor and a
separate decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bxVrnCNsXW3RvwmLc9Kvy
@Reefact
Reefact force-pushed the claude/sonar-points-restants-12zlnr branch from 317a161 to 9ccacfc Compare July 29, 2026 17:16
@Reefact
Reefact enabled auto-merge July 29, 2026 17:17
@Reefact
Reefact merged commit 4d06400 into main Jul 29, 2026
29 checks passed
@Reefact
Reefact deleted the claude/sonar-points-restants-12zlnr branch July 29, 2026 17:23
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