Skip to content

[CHORE] Catch duplicate YAML keys in pre-commit - #2340

Open
vishnuszipstack wants to merge 1 commit into
mainfrom
chore/yamllint-duplicate-keys
Open

vishnuszipstack wants to merge 1 commit into
mainfrom
chore/yamllint-duplicate-keys

Conversation

@vishnuszipstack

Copy link
Copy Markdown
Contributor

What

  • Adds yamllint (pinned v1.38.0) to .pre-commit-config.yaml with the key-duplicates rule and nothing else.
  • Renamed in the hook list to check yaml for duplicate keys, so a failure says what it is.

Why

check-yaml cannot catch a duplicate mapping key:

  • it runs with args: [--unsafe], which parses for syntax without constructing the document;
  • and even without that flag it loads via yaml.safe_load, which resolves a duplicate to the last occurrence silently.

Docker Compose's loader rejects duplicates outright. So the two loaders disagree, and the strict one is the one that runs in CI.

That gap shipped a tests/compose/docker-compose.test.yaml carrying a second backend: key alongside the one at line 5. pre-commit passed it; the e2e tier then died at compose up in 0.4s, before a single container started:

line 93: mapping key "backend" already defined at line 5

The failure mode is nasty because the local signal is green and the file looks fine — the second key simply replaces the first, taking the service's image: pin and every other env var with it.

How

- repo: https://github.com/adrienverge/yamllint
  rev: v1.38.0
  hooks:
    - id: yamllint
      name: check yaml for duplicate keys
      args: ["-d", "{rules: {key-duplicates: enable}}"]

The rule is named inline rather than in a .yamllint.yaml, so the one rule in force is visible at the point of use and a future rev bump cannot widen the scope.

Every other yamllint rule is off, deliberately. The repo has no yamllint baseline and this hook is not an invitation to adopt one: extends: default reports 333 findings across the same files (270 of them line-length), which would get the hook disabled rather than the files fixed.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

  • No runtime code is touched — the change is confined to .pre-commit-config.yaml.
  • The only risk is the hook failing on existing files. It does not: all 22 tracked YAML files report zero findings, so no exclusions and no per-file waivers are needed.
  • It can newly block a commit, but only for a file that Docker Compose (and Kubernetes, and GitHub Actions) would reject anyway.
  • types: [file, yaml] is upstream's own selector; no custom files: pattern to drift.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

Related Issues or PRs

Dependencies Versions

  • yamllint v1.38.0 (released 2026-01-13), new pre-commit hook repo. No application dependency is added — pre-commit builds it in its own isolated env.

Notes on Testing

  • The test that matters: reintroduced the exact incident shape and confirmed the hook fails, naming file, line and key — while check-yaml passes on that same file. A guard that does not fail on the bug it targets is worthless.
  • Confirmed .yml is covered as well as .yaml, by planting a duplicate in .github/ISSUE_TEMPLATE/config.yml. A hook that matches no files also reports "Passed", so this needed checking separately.
  • pre-commit run yamllint --all-files passes on the current tree (22 files, 0 findings).
  • pre-commit validate-config passes.
  • Verified the hook runs as part of a real commit, alongside the existing check yaml.

To reproduce the catch yourself:

printf '\nservices:\n  backend:\n    image: x\n' >> tests/compose/docker-compose.test.yaml
pre-commit run yamllint --all-files   # fails
pre-commit run check-yaml --all-files # passes
git checkout tests/compose/docker-compose.test.yaml

Screenshots

N/A

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

`check-yaml` cannot catch a duplicate mapping key. It runs with `--unsafe`,
which parses for syntax without constructing the document, and even without
that flag it loads via `yaml.safe_load`, which resolves a duplicate to the
last occurrence silently. Docker Compose's loader rejects them outright, so
the two disagree -- and the strict one is the one in CI.

That gap shipped a `tests/compose/docker-compose.test.yaml` carrying a
second `backend:` key alongside the one at line 5. pre-commit passed it;
the e2e tier then died at `compose up` in 0.4s, before a container started:

    line 93: mapping key "backend" already defined at line 5

Adds yamllint pinned at v1.38.0 with `key-duplicates` and nothing else.

Verified, rather than assumed:
- Reintroduced the exact incident shape; the hook FAILS naming the file,
  line and key, while `check-yaml` PASSES on that same file. A guard that
  does not fail on the bug it targets is worthless, so this is the test
  that mattered.
- Covers `.yml` as well as `.yaml` -- confirmed by planting a duplicate in
  `.github/ISSUE_TEMPLATE/config.yml`, since a hook matching no files also
  reports "Passed".
- Clean on the current tree: all 22 tracked YAML files, zero findings, so
  no exclusions and no per-file waivers.

Every other yamllint rule is off deliberately. `extends: default` reports
333 findings across those same files (270 of them line-length), which would
get the hook disabled rather than the files fixed. Listing the one rule
explicitly also means a future version bump cannot widen its scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@greptile-apps

greptile-apps Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

via Greptile

RetriggerConfidence Score: 5/5

[Low impact] This PR appears safe to merge; no actionable issues were found.

Summary

Adds a pinned yamllint pre-commit hook to catch duplicate YAML keys.

  • Pre-commit now flags YAML files with repeated mapping keys.

Reviews (1) · Last reviewed commit: "[CHORE] Catch duplicate YAML keys in pre..." · Reviewed by Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
✅ e2e-api-deployment e2e 3 0 0 0 9.6
✅ e2e-coowners e2e 1 0 0 0 0.9
✅ e2e-etl e2e 1 0 0 0 11.5
✅ e2e-login e2e 2 0 0 0 0.7
✅ e2e-prompt-studio e2e 1 0 0 0 3.9
✅ e2e-smoke e2e 2 0 0 0 0.8
✅ e2e-workflow e2e 1 0 0 0 9.8
✅ frontend unit 625 0 0 0 18.5
✅ integration-backend integration 638 0 0 26 60.5
✅ integration-connectors integration 1 0 0 7 9.5
✅ integration-workers integration 164 0 0 1 39.7
❌ ui e2e 0 1 0 0 0.0
✅ unit-backend unit 1567 0 0 1 50.6
✅ unit-connectors unit 82 0 0 0 11.4
✅ unit-core unit 281 0 0 0 3.2
✅ unit-platform-service unit 18 0 0 0 2.7
✅ unit-rig unit 120 0 0 0 4.5
✅ unit-runner unit 12 0 0 0 3.0
✅ unit-sdk1 unit 843 0 0 0 40.5
✅ unit-tool-sidecar unit 8 0 0 0 1.8
✅ unit-workers unit 1432 0 0 1 133.6
TOTAL 5802 1 0 36 416.7

Critical paths

⚠️ Critical paths not yet covered

  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • co-owner-manage — covered by integration-backend, e2e-coowners
  • workflow-create-execute — covered by e2e-workflow
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • api-deployment-run — covered by e2e-api-deployment
  • mcp-server-auth — covered by integration-backend
  • mcp-platform-auth — covered by integration-backend
  • platform-key-whoami — covered by integration-backend
  • prompt-studio-author — covered by integration-backend
  • prompt-studio-fetch-response — covered by e2e-prompt-studio
  • connector-register-test — covered by integration-backend
  • pipeline-etl-execute — covered by e2e-etl
  • usage-aggregate-read — covered by integration-backend
  • usage-token-tracking — covered by e2e-api-deployment
  • callback-result-delivery — covered by e2e-api-deployment

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.

1 participant