fix(ci): remove duplicate job keys — nine workflows here fail with zero jobs - #681
Conversation
…ro jobs GitHub Actions REJECTS a workflow containing duplicate keys. The run is recorded as `failure` with no jobs, no log and no check run, so the board shows a red mark with nothing behind it to read. Nine workflows here are in that state. The clearest casualty is CodeQL, whose `analyze` job declared `timeout-minutes` twice: it has 18 failures and 12 startup_failures against ZERO successes — this repository has never once been scanned by its own CodeQL workflow. WHY NOTHING CAUGHT IT. Python's yaml.safe_load silently keeps the LAST duplicate and reports success, so the file "parses". Every ordinary validation — the estate workflow linter, my own sweep checks — is structurally blind to this. Detecting it needs a loader that rejects duplicates explicitly. 15 duplicate job-level keys removed across 8 files, plus codeql.yml: ci.yml 4 rust-test, rust-test-coverage, haskell-build, ci-status security-policy.yml 3 rust-deny, semgrep, codeql release.yml 2 build-rust, github-release language-blockers.yml 2 check, ts_check codeql.yml 1 analyze dependabot-automerge.yml 1 automerge dogfood-gate.yml 1 dogfood-summary quality.yml 1 deploy-pages scorecard.yml 1 scorecard — a duplicated `uses:` on a reusable caller The fix is JOB-SCOPED, not file-scoped. These workflows legitimately have many jobs each with its own timeout-minutes, and timeout-minutes is also legal on a step, so "remove the second occurrence in the file" would have deleted real ones. Only keys at job-key indentation, within one job, are deduplicated, and the FIRST is kept. Keeping the first means the longer timeout survives in most cases. That is deliberate: these workflows have never executed, so there is no evidence about how long they actually take, and a spurious timeout on a newly-resurrected workflow would read as this change having broken something. ONE LINE WAS NOT A JOB KEY AT ALL. In security-policy.yml a `timeout-minutes: 10` had been inserted into the middle of a comment banner BETWEEN two jobs, swallowing the banner's closing line onto its own and leaving a duplicated comment. Only a text-level insertion can do that — a parser-aware edit cannot put a key inside a comment. The banner is restored. Checked estate-wide for further damage of that kind: 2 suspicious lines across 182 repository checkouts, this one and a cosmetic duplicated comment elsewhere. The blind insertion was not widespread; the duplicate keys are concentrated almost entirely in this repository. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
|
Note Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime. Code Review ✅ ApprovedRemoves duplicate job-level keys across nine silently failing GitHub Actions workflows to enable correct execution and scanning. No issues found.
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Important Your trial ends in 4 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more. Was this helpful? React with 👍 / 👎 | Gitar |
GitHub Actions **rejects a workflow containing duplicate keys** outright. The run is recorded as `failure` with **no jobs, no log and no check run** — a red mark with nothing behind it, and `gh pr checks` shows no row at all. ## Nothing in the toolchain could see this `yaml.safe_load` **silently keeps the last duplicate** and reports success. The file "parses", so every other lint passes. The workflow linter, the lockfile verifier, and my own sweep validation were all structurally blind to it. Measured 2026-08-05: **nine workflows in `hypatia`** were dead this way — including a CodeQL workflow with 18 failures, 12 startup_failures and **zero successes in its entire lifetime**. The repository had never once been scanned by its own scanner. Fix in hyperpolymath/hypatia#681. ## What this adds `scripts/check-workflow-duplicate-keys.py` — a `SafeLoader` subclass that raises on duplicate mapping keys instead of collapsing them — wired into the **`workflow-lint` job of `governance-reusable`**, so every consuming repository gets it. It emits `::error file=` annotations, so a failure lands **on the diff** rather than only in a log nobody opens. The script is pulled by sparse checkout rather than inlined, matching the pattern `allowlist-preflight` already uses in this same file: one source of truth, so the rule cannot drift between the copy that runs and the copy people read. ## Expect some repositories to go red A duplicate key means those workflows are **already failing** — silently, with nothing to read. Making it visible is the point, and the failure predates this check. **I am sweeping the remaining ~242 repository checkouts before this is merged**, so the blast radius is known rather than discovered. Current evidence says duplicates are concentrated almost entirely in `hypatia` (15 of 16 found across 182 checkouts), but that is a hypothesis until the rest are measured. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
GitHub Actions rejects a workflow containing duplicate keys. The run is recorded as
failurewith no jobs, no log and no check run — a red mark with nothing behind it to read.Nine workflows here are in that state. The clearest casualty is CodeQL, whose
analyzejob declaredtimeout-minutestwice:Why nothing caught it
Python's
yaml.safe_loadsilently keeps the last duplicate and reports success — so the file "parses". The estate workflow linter, and my own sweep validation, are both structurally blind to this. Catching it requires a loader that rejects duplicates explicitly.What was removed
15 duplicate job-level keys across 9 files:
ci.ymlsecurity-policy.ymlrelease.ymllanguage-blockers.ymlcodeql.ymldependabot-automerge.ymldogfood-gate.ymlquality.ymlscorecard.ymluses:on a reusable callerThe fix is job-scoped, not file-scoped
These workflows legitimately have many jobs each with its own
timeout-minutes, andtimeout-minutesis also legal on a step. So "remove the second occurrence in the file" would have deleted real ones. Only keys at job-key indentation, within a single job, are deduplicated — and the first is kept.Keeping the first means the longer timeout survives in most cases. That is deliberate: these workflows have never executed, so there is no evidence about how long they actually take, and a spurious timeout on a newly-resurrected workflow would read as this change having broken something.
One line was not a job key at all
In
security-policy.yml, atimeout-minutes: 10had been inserted into the middle of a comment banner between two jobs — swallowing the banner's closing line onto its own and leaving a duplicated comment:Only a text-level insertion can do that; a parser-aware edit cannot place a key inside a comment. The banner is restored.
I checked estate-wide for more damage of that kind: 2 suspicious lines across 182 repository checkouts — this one, and a cosmetic duplicated comment elsewhere. The blind insertion was not widespread, and the duplicate keys are concentrated almost entirely in this repository.
Expect this repo to get louder
Nine workflows that have been failing silently will now actually run. Some will find real problems — CodeQL in particular has never scanned this codebase.
🤖 Generated with Claude Code