perf(ci): short-circuit hygiene's docs-irrelevant linters on docs-only diffs (#628) - #659
Conversation
…y diffs (#628) Gate the hygiene job's docs-irrelevant full-repo linters — ShellCheck, actionlint, the four check-jsonschema steps, and exec-bit — behind the existing self-tested, fail-closed check-docs-only detector so a diff confined to the docs-only allowlist reports an honest evaluated-and-not-applicable success instead of running ~53s of checks it cannot affect. The job never skips: the detector self-test runs unconditionally, only the inner steps carry the conditional, and each gated step's intentional docs-only skip is mapped to `success` in the CHECK_RESULTS feed (where the step provably did not run) — never by weakening scripts/aggregate-hygiene-results.sh, which stays fail-closed on any real non-success outcome including `skipped`. The docs linters (markdown, typos, editorconfig, gitleaks, eol-renormalize, comment-hygiene, machine-specific-paths) stay unconditional. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 46s —— View job PR Review
This is a clean, well-reasoned extension of the established Gate logic — correctThe seven gated steps (lines 93–148) all use
The empty-output case on push to main is the implicit fail-closed path — no special handling needed. Aggregator ternary — correct and honestThe ternary pattern (lines 206–212): shellcheck=${{ steps.scope.outputs.docs_only == 'true' && 'success' || steps.shellcheck.outcome }}The ternary condition ( Push-to-main trace: Scope step failure behavior — correctThe Test coverage — completeThe three new cases (lines 100–102) correctly pin the three remaining The PASS=19 total (was 16 + 3 new) matches what the test file contains. docs-only-paths.txt comment — accurateThe updated comment (lines 14–28) correctly enumerates the hygiene subset's inertness argument and explicitly calls out that the docs-linting steps (markdown, typos, editorconfig, gitleaks, eol-renormalize, comment-hygiene, machine-specific-paths) remain unconditional. The allowlist itself is unchanged ( Minor observations (no action required)Self-test triplication: The
SummaryThe implementation is logically correct, consistent with the established precedent, and the inertness proof is complete (gate + ternary are inverses, allowlist is positive-only, test suite pins every gated input). No issues found. |
Post-green review classificationNo findings. Reviewer verified gate/ternary inverse-correctness, fail-closed behavior on scope-step failure and push-to-main, and complete test coverage for the newly-gated jsonschema inputs. Applied Orchestrator's own diff read (rule 10, cross-plugin-contract blast radius — this affects every plugin's PR checks) confirms the review bot's assessment independently. CI green (16/16). Flipping to ready for review. |
|
Claude finished @kyle-sexton's task in 1m 52s —— View job PR Review
This is a clean extension of the established Gate logic — correctThe seven gated steps (lines 93–148) all use
Aggregator ternary — correct and honestThe ternary pattern (lines 206–212): shellcheck=${{ steps.scope.outputs.docs_only == 'true' && 'success' || steps.shellcheck.outcome }}The ternary condition (
Test coverage — completeThe three new cases (lines 100–102) correctly cover the remaining assert_flag "marketplace manifest (marketplace-schema reads it)" false ".claude-plugin/marketplace.json"
assert_flag "plugin manifest (plugin-schema reads it)" false "plugins/p1/.claude-plugin/plugin.json"
assert_flag "dependabot.yml (dependabot-schema reads it)" false ".github/dependabot.yml"These paths aren't seeded in docs-only-paths.txt comment — accurateThe updated inertness-proof comment (lines 19–28) correctly enumerates the hygiene subset (ShellCheck, actionlint, the four Minor observations (no action required)Self-test triplication:
No SummaryImplementation is logically correct at every path (PR docs-only, PR code diff, push to main, detector failure). The gate and ternary are exact inverses, the aggregator is untouched and still fails closed on any real non- |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1685b5f90c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…cs-irrelevant The docs-only short-circuit gated ShellCheck and exec-bit behind `docs_only != 'true'` and mapped their skip to `success`, but both scan the WHOLE repo, not a code-scoped subtree, so the inertness proof they were gated under is false: - exec-bit runs `git grep --cached '^#!' -- .` (default paths `.`), extension- and directory-agnostic — it flags any tracked shebang file recorded 100644 anywhere, including under docs/topics/. - ShellCheck's git-tracked discovery runs `git ls-files -- '*.sh' '*.bash'` repo-wide — it lints any tracked shell script anywhere, including under docs/topics/. A docs-only PR adding a mode-100644 shebang file (or a *.sh) under the docs/topics/ allowlist therefore skipped both checks and the ternary mapped them to `success` — a fail-closed hole that let a file the commit procedure requires exec-bit to catch merge unlinted. Ungate both and feed their raw `.outcome`; they now run on every diff. The other five gated steps stay gated: actionlint auto-discovers only `.github/workflows/*` and the four check-jsonschema steps pass explicit fixed `files:` — none can match a docs/topics/ path, so they are genuinely inert. Corrects the inertness prose in the ci.yml comments and scripts/docs-only-paths.txt to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 12s —— View job PR Review
The fix commit ( What the fix doesRemoves the Correctness — four-case truth table
The fail-closed hole is closed. No gate → no ternary override → no masked outcome. Five remaining gated steps ( These all read fixed, path-scoped inputs ( actionlint=${{ steps.scope.outputs.docs_only == 'true' && 'success' || steps.actionlint.outcome }}On push to main, Aggregator feed — correctshellcheck=${{ steps.shellcheck.outcome }} # raw — unconditional step
...
exec-bit=${{ steps.exec_bit.outcome }} # raw — unconditional stepNo ternary, no override path. Aggregator sees the real outcome in every scenario. Prose corrections — accurate
Report step log message (line 185): Updated to name only Test suite — no changes needed (observation only)The Minor: the comment at lines 96–98 still reads "hygiene job's docs-irrelevant step set" and mentions "ShellCheck (scripts/, plugins/**)." With ShellCheck now unconditional, this label is stale — ShellCheck no longer belongs to the gated set at all. The cases themselves are correct; only the comment header is inaccurate. Low priority to fix, but worth noting. SummaryThe fix is correct, minimal, and closes the fail-closed hole identified in the prior review without introducing new issues. The five path-scoped gated steps are correctly retained. The inertness proof in One cosmetic follow-up if desired: update the |
Summary
Docs-only PRs still paid the
hygienejob's path-scoped linter cost —actionlintand fourcheck-jsonschemasteps — for a diff that provably cannot affect any of them. #617 (via #622) short-circuitedplugin-gateandmiro-plugin, which cut runner-minutes but not the merge-latency bottleneck. This applies the SAME self-test-first, fail-closed, inner-step-gated discipline tohygiene's genuinely path-scoped subset, so those runs report an honest evaluated-and-not-applicable success instead of running.ShellCheckandexec-bitare deliberately NOT gated: both scan the whole repo extension- and directory-agnostically (ShellChecklints every tracked*.sh/*.bashviagit ls-files;exec-bitflags every tracked shebang file recorded100644viagit grep -- .), so a shell or shebang file added under an otherwise docs-only prefix likedocs/topics/is real input they must still catch. They run on every diff alongside the docs-linting checks that read the changed docs directly:markdown,typos,editorconfig,gitleaks,eol-renormalize,comment-hygiene,machine-specific-paths.Fix
Reuses the existing
scripts/check-docs-only.shdetector andscripts/docs-only-paths.txtallowlist from #622 unchanged — no new detector.In the
hygienejob (.github/workflows/ci.yml):Added the always-run
Test the docs-only detectorself-test and a PR-onlyDetect a docs-only diffstep (id: scope), mirroringplugin-gate/miro-plugin. The checkout already hadfetch-depth: 0, so the base ref resolves.Gated exactly the five path-scoped steps with
if: steps.scope.outputs.docs_only != 'true':actionlint,marketplace_schema,plugin_schema,dependabot_schema,workflow_schema.shellcheckandexec_bitstay unconditional.The job never skips and the aggregator is never weakened. The
hygienejob usescontinue-on-error: trueper step plus aCHECK_RESULTSfeed intoscripts/aggregate-hygiene-results.sh, which fails closed on any non-successoutcome (includingskipped). A gated step that skips would therefore red the job — so each gated entry inCHECK_RESULTSmaps the intentional docs-only skip tosuccessin the expression layer:actionlint=${{ steps.scope.outputs.docs_only == 'true' && 'success' || steps.actionlint.outcome }}This is honest, not a swallow: the ternary condition (
docs_only == 'true') is the exact inverse of each step's gate, sosuccessis fed ONLY for a step that provably did not run — no real outcome is ever masked. The two whole-repo scanners (shellcheck,exec-bit) feed their raw.outcome. The aggregator script is untouched and still fails closed on any realfailure(or askippedit isn't told to tolerate). This is the same evaluated-and-not-applicable successplugin-gatereports with its log step, expressed here through the feed the hygiene job already uses.Added an explicit
Report docs-irrelevant checks not applicablelog step (gated ondocs_only == 'true') so a human reading a green run understands why those checks show skipped.Also extended the two non-workflow artifacts per the established two-part-proof discipline:
scripts/docs-only-paths.txt— updated the inertness-proof comment to name only the genuinely path-scoped gated subset (actionlint+ the four schema checks, none of which readdocs/topics/) and to state explicitly thatShellCheckandexec-bitscan the whole repo and stay unconditional (as do the docs-linters). Allowlist itself unchanged (stilldocs/topics/only — no growth).scripts/check-docs-only.test.sh— added three cases pinning the remainingcheck-jsonschemainputs tofalse(.claude-plugin/marketplace.json,plugins/*/.claude-plugin/plugin.json,.github/dependabot.yml); theactionlint/ workflow-schema inputs (.github/workflows/ci.yml) were already pinned false. These fail the moment someone widens the allowlist to cover an input a gated check reads.Verification
Run in the worktree (Git Bash):
bash scripts/check-docs-only.test.sh→PASS=19 FAIL=0. Confirmsdocs/topics/-only →trueand every gated check's input (.github/workflows/ci.yml,.claude-plugin/marketplace.json,plugins/*/.claude-plugin/plugin.json,.github/dependabot.yml, lockfiles) →false.scripts/aggregate-hygiene-results.sh --self-test→Hygiene result aggregation contract passed.The aggregator is unchanged and still fails closed on a named empty/failure/non-successoutcome.actionlint .github/workflows/ci.yml→ clean. Validates the five added gate conditions and the five ternary expressions inCHECK_RESULTS.shellcheck --rcfile .shellcheckrcandshfmt -don the edited/reused scripts → clean, no diff.Honest scope of what this PR's own CI proves: this PR touches
.github/andscripts/, so on itdocs_only=falseand the full hygiene suite runs — a live proof only of the run-path (nothing is wrongly skipped on a code diff). The skip-path is not live-observable here by construction; it is proven by the detector unit test (docs/topics/-only →true) plus the gate/expression logic being the exact inverse of each other. Correctness of the intentional-skip →successmapping rests on that unit test and the expression, not on a fabricated observation.Versioning/changelog: none. No
plugins/<name>/directory is touched — this is a repo-root CI-only change — so per the precedent #622 followed (matching CI-only #490/#593) there is no plugin version bump or CHANGELOG entry.Closes #628
Related
scripts/check-docs-only.shdetector, same self-test-first / fail-closed / inner-step-gated discipline, applied tohygieneinstead ofplugin-gate/miro-plugin.skipped) is exactly that shape and is deliberately avoided: the job always runs, the detector self-tests first, and only inner steps are gated with their skip reported as an honest not-applicable success.🤖 Generated with a Claude Code implementation subagent (issue #628)