Skip to content

fix(ci): remove duplicate job keys — nine workflows here fail with zero jobs - #681

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/duplicate-job-keys
Aug 6, 2026
Merged

fix(ci): remove duplicate job keys — nine workflows here fail with zero jobs#681
hyperpolymath merged 1 commit into
mainfrom
fix/duplicate-job-keys

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

GitHub Actions rejects a workflow containing duplicate keys. The run is recorded as failure with 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 analyze job declared timeout-minutes twice:

18 failures, 12 startup_failures, 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". 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:

file count jobs
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 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 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, 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:

  # ============================================================================
  # Secret Detection
    timeout-minutes: 10  # ============================================================================
  # Secret Detection
  # ============================================================================

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

…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>
@gitar-bot

gitar-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

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.
Learn more

Code Review ✅ Approved

Removes duplicate job-level keys across nine silently failing GitHub Actions workflows to enable correct execution and scanning. No issues found.

Auto-approved and auto-merge armed: No blocking issues found.
Please see Auto-approve Docs for details on setting custom approval criteria. — merges when pipeline and required approvals pass.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

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

@gitar-bot
gitar-bot Bot enabled auto-merge (squash) August 6, 2026 01:54

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gitar has auto-approved this PR and enabled auto-merge (configure)

@gitar-bot gitar-bot Bot added the gitar-approved Added by Gitar label Aug 6, 2026
@hyperpolymath
hyperpolymath merged commit e088405 into main Aug 6, 2026
51 of 77 checks passed
@hyperpolymath
hyperpolymath deleted the fix/duplicate-job-keys branch August 6, 2026 04:58
hyperpolymath added a commit to hyperpolymath/standards that referenced this pull request Aug 6, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gitar-approved Added by Gitar

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant