From 1ce7cb37a33818e7bcfc74a8a0e9a3220788023c Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sat, 17 Oct 2026 09:41:11 +0000 Subject: [PATCH] fix(ci): open a new coreutils drift PR when the previous one was closed `gh pr view ` resolves a pull request for that branch in any state. Once the drift PR was closed unmerged it kept matching, so every later weekly run force-pushed the branch and edited that closed PR instead of opening a new one: the run went green and the drift never surfaced. #2161 was closed in July 2026 and silently absorbed every run after it, including today's, which regenerated 20 generated files. Look up open PRs only (`gh pr list --head --state open`) and create one otherwise. Regression test executes the workflow step with stubbed `gh`/`git`: with only a closed PR matching, the step must call `pr create`, not `pr edit`. Claude-Session: https://claude.ai/code/session_01XD2n4RyeStFm5WagYgg4ch --- .github/workflows/coreutils-args-drift.yml | 12 ++- knowledge/runtimes/coreutils-args-port.md | 8 ++ scripts/tests/test_coreutils_drift_pr.py | 94 ++++++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 scripts/tests/test_coreutils_drift_pr.py diff --git a/.github/workflows/coreutils-args-drift.yml b/.github/workflows/coreutils-args-drift.yml index 0d5c822aa..79c13183e 100644 --- a/.github/workflows/coreutils-args-drift.yml +++ b/.github/workflows/coreutils-args-drift.yml @@ -327,8 +327,16 @@ jobs: Design: [\`knowledge/runtimes/coreutils-args-port.md\`](../tree/main/knowledge/runtimes/coreutils-args-port.md). EOF - if gh pr view "$branch" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh pr edit "$branch" --repo "$GITHUB_REPOSITORY" \ + # Match only an OPEN PR. `gh pr view "$branch"` resolves a PR for + # the branch in any state, so once a drift PR is closed unmerged it + # keeps matching and every later run edits that closed PR instead of + # opening a new one — the drift then never surfaces again. #2161 was + # closed in July and absorbed every run since. + open_pr="$(gh pr list --repo "$GITHUB_REPOSITORY" \ + --head "$branch" --state open --json number \ + --jq '.[0].number // empty')" + if [ -n "$open_pr" ]; then + gh pr edit "$open_pr" --repo "$GITHUB_REPOSITORY" \ --title "$title" \ --body-file "$body_file" else diff --git a/knowledge/runtimes/coreutils-args-port.md b/knowledge/runtimes/coreutils-args-port.md index fbc0da3d6..3f498dade 100644 --- a/knowledge/runtimes/coreutils-args-port.md +++ b/knowledge/runtimes/coreutils-args-port.md @@ -398,6 +398,14 @@ and on `workflow_dispatch`: credentials, applies only generated-file changes from that patch, and opens or updates the drift PR with `gh`. +The PR lookup matches **open** PRs only (`gh pr list --head --state +open`). `gh pr view ` resolves a PR for the branch in any state, so +once a drift PR is closed unmerged it keeps matching and every later run edits +that closed PR instead of opening a new one — the branch is force-pushed, the +run goes green, and the drift never surfaces. #2161 was closed in July 2026 +and silently absorbed every run after it. Regression test: +`scripts/tests/test_coreutils_drift_pr.py`. + The read-only job is the only job that builds or executes uutils code. The write-scoped job must not checkout, build, or execute uutils code, and must not use third-party PR-creation actions. Module diffs and args diffs land in diff --git a/scripts/tests/test_coreutils_drift_pr.py b/scripts/tests/test_coreutils_drift_pr.py new file mode 100644 index 000000000..be92d3d01 --- /dev/null +++ b/scripts/tests/test_coreutils_drift_pr.py @@ -0,0 +1,94 @@ +"""The coreutils drift run must open a PR when the previous one was closed. + +`gh pr view ` resolves a pull request for that branch in ANY state. +Once a drift PR is closed unmerged, it keeps matching forever, so the weekly +run silently edited that closed PR instead of opening a new one and the drift +stopped surfacing. These tests execute the workflow's own `run` script with +stubbed `gh`/`git` to pin the open-PR-only lookup. +""" +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +WORKFLOW = ROOT / '.github/workflows/coreutils-args-drift.yml' + +def _write(path: Path, body: str) -> None: + path.write_text(body) + path.chmod(0o755) + + +class CoreutilsDriftPrTests(unittest.TestCase): + def setUp(self): + jobs = yaml.safe_load(WORKFLOW.read_text())['jobs'] + self.step = next( + step + for job in jobs.values() + for step in job.get('steps', []) + if step.get('name') == 'Commit and open PR' + ) + + def _run(self, *, open_pr_exists: bool): + """Run the step with a closed PR present and `open_pr_exists` open ones.""" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + bin_dir = root / 'bin' + bin_dir.mkdir() + log = root / 'gh.log' + open_number = '4242' if open_pr_exists else '' + + # `gh pr view` always succeeds (the closed PR still matches), so a + # lookup that trusts it cannot tell closed from open. + _write(bin_dir / 'gh', f'''#!/bin/sh +printf '%s\\n' "$*" >> {log} +case "$1 $2" in + "auth setup-git") exit 0 ;; + "pr list") printf '%s' '{open_number}' ; exit 0 ;; + "pr view") exit 0 ;; + "pr edit"|"pr create") exit 0 ;; +esac +exit 0 +''') + _write(bin_dir / 'git', '''#!/bin/sh +exit 0 +''') + + env = { + 'PATH': f"{bin_dir}:{os.environ['PATH']}", + 'HOME': str(root), + 'GITHUB_REPOSITORY': 'everruns/bashkit', + 'GH_TOKEN': 'stub', + 'RUNNER_TEMP': str(root), + 'NEW_REV': 'abc1234', + 'OLD_REV': 'def5678', + 'UTILS': 'cat tac', + 'MODULES': 'format', + } + result = subprocess.run( + ['/bin/bash', '-c', self.step['run']], + env=env, capture_output=True, text=True, cwd=root, + ) + self.assertEqual(result.returncode, 0, result.stderr) + return log.read_text() if log.exists() else '' + + def test_opens_a_new_pr_when_only_a_closed_one_matches(self): + calls = self._run(open_pr_exists=False) + self.assertIn('pr create', calls, calls) + self.assertNotIn('pr edit', calls, calls) + + def test_updates_the_existing_pr_when_one_is_open(self): + calls = self._run(open_pr_exists=True) + self.assertIn('pr edit', calls, calls) + self.assertNotIn('pr create', calls, calls) + + def test_open_state_is_scoped_in_the_lookup(self): + self.assertIn('--state open', self.step['run']) + + +if __name__ == '__main__': + unittest.main()