From ce7f3576454a38f0f18e3eb7ce25bee904a29e7c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:05:34 +0900 Subject: [PATCH 1/5] fix(ci): skip docs-only changes for clusterfuzzlite and container-image Org-wide audit found these workflows had no paths-ignore filter, triggering full CI on every push/PR including docs-only changes and adding to org Actions queue congestion. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/clusterfuzzlite.yml | 3 +++ .github/workflows/container-image.yml | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/clusterfuzzlite.yml b/.github/workflows/clusterfuzzlite.yml index b70a6263..56df05e3 100644 --- a/.github/workflows/clusterfuzzlite.yml +++ b/.github/workflows/clusterfuzzlite.yml @@ -2,6 +2,9 @@ name: clusterfuzzlite on: pull_request: + paths-ignore: + - "docs/**" + - "*.md" workflow_dispatch: permissions: diff --git a/.github/workflows/container-image.yml b/.github/workflows/container-image.yml index 4b027fba..6a605327 100644 --- a/.github/workflows/container-image.yml +++ b/.github/workflows/container-image.yml @@ -2,9 +2,15 @@ name: container-image on: pull_request: + paths-ignore: + - "docs/**" + - "*.md" push: tags: - 'v*' + paths-ignore: + - "docs/**" + - "*.md" workflow_dispatch: inputs: publish_nvidia: From d978f703fd49d8af7666fbf853e91d3da0bb20c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 13:17:29 +0900 Subject: [PATCH 2/5] fix(ci): make docs-only filters match GitHub path semantics --- .github/workflows/clusterfuzzlite.yml | 2 +- .github/workflows/container-image.yml | 5 +---- tests/test_ci_path_filter_contract.py | 31 +++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 tests/test_ci_path_filter_contract.py diff --git a/.github/workflows/clusterfuzzlite.yml b/.github/workflows/clusterfuzzlite.yml index 56df05e3..08af3ced 100644 --- a/.github/workflows/clusterfuzzlite.yml +++ b/.github/workflows/clusterfuzzlite.yml @@ -4,7 +4,7 @@ on: pull_request: paths-ignore: - "docs/**" - - "*.md" + - "**.md" workflow_dispatch: permissions: diff --git a/.github/workflows/container-image.yml b/.github/workflows/container-image.yml index 6a605327..1ecdb511 100644 --- a/.github/workflows/container-image.yml +++ b/.github/workflows/container-image.yml @@ -4,13 +4,10 @@ on: pull_request: paths-ignore: - "docs/**" - - "*.md" + - "**.md" push: tags: - 'v*' - paths-ignore: - - "docs/**" - - "*.md" workflow_dispatch: inputs: publish_nvidia: diff --git a/tests/test_ci_path_filter_contract.py b/tests/test_ci_path_filter_contract.py new file mode 100644 index 00000000..65d3accb --- /dev/null +++ b/tests/test_ci_path_filter_contract.py @@ -0,0 +1,31 @@ +"""Contracts for documentation-only GitHub Actions filtering.""" + +from pathlib import Path + +import pytest + + +@pytest.mark.parametrize( + "workflow_path", + ( + ".github/workflows/clusterfuzzlite.yml", + ".github/workflows/container-image.yml", + ), +) +def test_pr_filters_ignore_markdown_at_any_depth(workflow_path: str) -> None: + """PR filters cover nested Markdown instead of repository-root files only.""" + workflow = Path(workflow_path).read_text(encoding="utf-8") + + assert ' - "**.md"' in workflow + assert ' - "*.md"' not in workflow + + +def test_tagged_container_release_does_not_claim_path_filtering() -> None: + """Tag pushes retain their release trigger without an ineffective path filter.""" + workflow = Path(".github/workflows/container-image.yml").read_text(encoding="utf-8") + push_section = workflow.split(" push:\n", maxsplit=1)[1].split( + " workflow_dispatch:\n", maxsplit=1 + )[0] + + assert "paths-ignore:" not in push_section + assert "tags:\n - 'v*'" in push_section From 9e8bbe744542e5816f21f28130990c659eb8e424 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 13:42:13 +0900 Subject: [PATCH 3/5] test(ci): require docs-only filters on each PR trigger --- tests/test_ci_path_filter_contract.py | 52 ++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/tests/test_ci_path_filter_contract.py b/tests/test_ci_path_filter_contract.py index 65d3accb..ac117468 100644 --- a/tests/test_ci_path_filter_contract.py +++ b/tests/test_ci_path_filter_contract.py @@ -5,6 +5,41 @@ import pytest +_DOC_ONLY_PATHS = {"docs/**", "manual/**", "**.md"} + + +def _event_block(workflow: str, event: str) -> list[str]: + """Return one peer event block from the workflow's top-level ``on`` mapping.""" + lines = workflow.splitlines() + marker = f" {event}:" + try: + start = lines.index(marker) + except ValueError as exc: + raise AssertionError(f"missing workflow event: {event}") from exc + + block: list[str] = [] + for line in lines[start + 1 :]: + if line.startswith(" ") and not line.startswith(" "): + break + block.append(line) + return block + + +def _paths_ignore(block: list[str]) -> set[str]: + """Read the ``paths-ignore`` list from one event block without quote coupling.""" + try: + start = block.index(" paths-ignore:") + except ValueError as exc: + raise AssertionError("event is missing paths-ignore") from exc + + entries: set[str] = set() + for line in block[start + 1 :]: + if not line.startswith(" - "): + break + entries.add(line.removeprefix(" - ").strip().strip("'\"")) + return entries + + @pytest.mark.parametrize( "workflow_path", ( @@ -12,20 +47,19 @@ ".github/workflows/container-image.yml", ), ) -def test_pr_filters_ignore_markdown_at_any_depth(workflow_path: str) -> None: - """PR filters cover nested Markdown instead of repository-root files only.""" +def test_pr_filters_ignore_all_documentation_assets(workflow_path: str) -> None: + """PR filters skip Markdown, generated docs, and non-Markdown manual assets.""" workflow = Path(workflow_path).read_text(encoding="utf-8") + pull_request = _event_block(workflow, "pull_request") - assert ' - "**.md"' in workflow - assert ' - "*.md"' not in workflow + assert _paths_ignore(pull_request) == _DOC_ONLY_PATHS def test_tagged_container_release_does_not_claim_path_filtering() -> None: """Tag pushes retain their release trigger without an ineffective path filter.""" workflow = Path(".github/workflows/container-image.yml").read_text(encoding="utf-8") - push_section = workflow.split(" push:\n", maxsplit=1)[1].split( - " workflow_dispatch:\n", maxsplit=1 - )[0] + push_section = _event_block(workflow, "push") - assert "paths-ignore:" not in push_section - assert "tags:\n - 'v*'" in push_section + assert " paths-ignore:" not in push_section + assert " tags:" in push_section + assert " - 'v*'" in push_section From b09ac9e9069215455d15a93236a196c254f09ffa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 13:42:29 +0900 Subject: [PATCH 4/5] fix(ci): skip manual assets in ClusterFuzzLite PRs --- .github/workflows/clusterfuzzlite.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/clusterfuzzlite.yml b/.github/workflows/clusterfuzzlite.yml index 08af3ced..d5d2a918 100644 --- a/.github/workflows/clusterfuzzlite.yml +++ b/.github/workflows/clusterfuzzlite.yml @@ -4,6 +4,7 @@ on: pull_request: paths-ignore: - "docs/**" + - "manual/**" - "**.md" workflow_dispatch: From a1910e652f9fcf28116175c7bda62af2b1fec296 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 13:42:53 +0900 Subject: [PATCH 5/5] fix(ci): skip manual assets in container PR builds --- .github/workflows/container-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/container-image.yml b/.github/workflows/container-image.yml index 1ecdb511..01e41aa7 100644 --- a/.github/workflows/container-image.yml +++ b/.github/workflows/container-image.yml @@ -4,6 +4,7 @@ on: pull_request: paths-ignore: - "docs/**" + - "manual/**" - "**.md" push: tags: