From 86420813e2d3a3fb499834342b99ff1f84b529c1 Mon Sep 17 00:00:00 2001 From: Sascha Ittner Date: Mon, 3 Aug 2026 17:34:00 +0200 Subject: [PATCH] Reject duplicate workflow keys instead of trusting safe_load The v6.18.40 build never started: my previous commit added a second env: block to the release step rather than merging into the existing one, and GitHub rejects that with "'env' is already defined" before running any job. Merged them. The reason it got pushed is the check I was relying on: yaml.safe_load accepts duplicate mapping keys and keeps the last one, so the file parsed cleanly here and was rejected there. scripts/lint-workflows.py now loads with a constructor that refuses duplicates, and reports the same location GitHub does - "duplicate key 'env' at line 170, column 9" against "(Line: 170, Col: 9): 'env' is already defined" - plus it fails a workflow with no jobs. Verified against a deliberately broken copy. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 1 - .github/workflows/ci.yml | 10 ++----- scripts/lint-workflows.py | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) create mode 100755 scripts/lint-workflows.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65cde63..4887953 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -167,7 +167,6 @@ jobs: env: GH_TOKEN: ${{ github.token }} TAG: ${{ needs.prepare.outputs.release_tag }} - env: KERNEL_REF: ${{ needs.prepare.outputs.kernel_ref }} KERNEL_SHA: ${{ needs.prepare.outputs.kernel_sha }} run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20676fa..beea281 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,14 +26,8 @@ jobs: shellcheck --version shellcheck scripts/*.sh packaging/*.sh - - name: workflows parse - run: | - python3 - <<'EOF' - import glob, yaml - for f in sorted(glob.glob(".github/workflows/*.yml")): - yaml.safe_load(open(f)) - print("ok", f) - EOF + - name: workflows + run: ./scripts/lint-workflows.py # Catches the failure mode this repo cares about most: Kconfig silently # dropping a requested option. Takes a few minutes, no compilation. diff --git a/scripts/lint-workflows.py b/scripts/lint-workflows.py new file mode 100755 index 0000000..de0fc62 --- /dev/null +++ b/scripts/lint-workflows.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Check the workflow files the way GitHub does, not the way PyYAML does. + +yaml.safe_load() accepts duplicate mapping keys and silently keeps the last +one, so a second `env:` in a step parses cleanly here and is then rejected by +GitHub with "'env' is already defined" - after a push, with zero jobs run and +no log to read. This loader refuses duplicates instead. +""" + +import glob +import sys + +import yaml + + +class StrictLoader(yaml.SafeLoader): + pass + + +def no_duplicates(loader, node, deep=False): + mapping = {} + for key_node, value_node in node.value: + key = loader.construct_object(key_node, deep=deep) + if key in mapping: + raise yaml.YAMLError( + "duplicate key %r at line %d, column %d" + % (key, key_node.start_mark.line + 1, key_node.start_mark.column + 1) + ) + mapping[key] = loader.construct_object(value_node, deep=deep) + return mapping + + +StrictLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, no_duplicates +) + + +def main(paths): + paths = paths or sorted(glob.glob(".github/workflows/*.yml")) + failed = False + for path in paths: + try: + doc = yaml.load(open(path), Loader=StrictLoader) + except yaml.YAMLError as exc: + print("FAIL %s: %s" % (path, exc)) + failed = True + continue + jobs = doc.get("jobs") or {} + if not jobs: + print("FAIL %s: no jobs defined" % path) + failed = True + continue + print("ok %s (%s)" % (path, ", ".join(jobs))) + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))