From 65c38fd89182103561aa0d87b5a75ea739b83bfe Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sat, 6 Jun 2026 20:20:11 -0500 Subject: [PATCH 1/5] test(dev-lead): assert per-issue/per-PR concurrency lanes, not per-repo serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #450 restored per-issue / per-PR concurrency lanes (the fix for issue pickups being cancelled by PR follow-up traffic, petry-projects/.github#402), but test_concurrency_config.sh still asserted the old single 'dev-lead' per-repo group from #278 — so it fails against the current dev-lead.yml (3 checks). It didn't go red on the #450 merge only because the push-to-main path filter excludes .github/workflows/dev-lead*.yml; it would fail on the next dev-lead PR. The #278 serialization existed only to stop concurrent in-place PR checkouts from corrupting the working tree. #449 (#448) removed that hazard by checking out each PR branch in an isolated git worktree, so parallel per-lane runs are now safe. Invert the assertions to require the per-issue / per-PR / ci-relay lanes and forbid a repo-wide 'dev-lead' group, and extend coverage to the reusable workflow (source of truth for all consumer stubs). Update the CI step label. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test-dev-lead.yml | 2 +- .../integration/test_concurrency_config.sh | 44 ++++++++++++++----- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-dev-lead.yml b/.github/workflows/test-dev-lead.yml index 5aeb74c39..2dfb2bdce 100644 --- a/.github/workflows/test-dev-lead.yml +++ b/.github/workflows/test-dev-lead.yml @@ -73,5 +73,5 @@ jobs: contents: read steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Validate concurrency config (per-repo serialization) + - name: Validate concurrency config (per-issue/per-PR lanes) run: bash tests/dev-lead/integration/test_concurrency_config.sh diff --git a/tests/dev-lead/integration/test_concurrency_config.sh b/tests/dev-lead/integration/test_concurrency_config.sh index e3da7f260..c71ddc1d9 100644 --- a/tests/dev-lead/integration/test_concurrency_config.sh +++ b/tests/dev-lead/integration/test_concurrency_config.sh @@ -1,6 +1,18 @@ #!/usr/bin/env bash -# Validates that dev-lead workflow files use per-repo serialized concurrency -# (a single 'dev-lead' group) rather than per-PR or per-issue groups. +# Validates that dev-lead workflow files use per-issue / per-PR concurrency lanes +# (dev-lead-issue-, dev-lead-pr-) plus a per-SHA ci-relay lane, rather than a +# single repo-wide 'dev-lead' group. +# +# History: #278 collapsed everything into one per-repo 'dev-lead' group so at most +# one dispatch ran at a time — needed only because the agent checked out PR branches +# IN PLACE, so concurrent runs corrupted each other's working tree. That serialization +# then starved issue pickups: PR follow-up traffic (reviews/comments/ci-relay) held the +# single slot and queued issue-labeled runs were superseded and cancelled before they +# could open a PR (petry-projects/.github#402). +# +# #449 (#448) removed the root hazard by checking out each PR branch in an isolated +# git worktree, so different issues/PRs can now run in parallel safely. #450 restored +# per-issue / per-PR lanes. This test guards that design against regression. set -euo pipefail FAIL=0 @@ -25,20 +37,28 @@ check_present() { fi } -WORKFLOW=".github/workflows/dev-lead.yml" +# Both the inline workflow and the reusable (source of truth for all consumer +# stubs) must carry the per-lane routing. +for WORKFLOW in \ + ".github/workflows/dev-lead.yml" \ + ".github/workflows/dev-lead-reusable.yml"; do + echo "── $WORKFLOW ──" -# Must not have per-PR or per-issue group keys (those allowed parallel runs) -check_absent "$WORKFLOW" "dev-lead-pr-" "no per-PR group" -check_absent "$WORKFLOW" "dev-lead-issue-" "no per-issue group" + # Per-issue and per-PR lanes must be present so a labeled-issue pickup is never + # cancelled by unrelated PR follow-up traffic. + check_present "$WORKFLOW" "dev-lead-pr-" "per-PR lane" + check_present "$WORKFLOW" "dev-lead-issue-" "per-issue lane" -# ci-relay must still get its own ephemeral per-SHA slot -check_present "$WORKFLOW" "dev-lead-ci-relay-" "ci-relay per-SHA group preserved" + # ci-relay keeps its own ephemeral per-SHA slot so it fires immediately. + check_present "$WORKFLOW" "dev-lead-ci-relay-" "ci-relay per-SHA lane" -# The fallback group must be the bare repo-scoped key -check_present "$WORKFLOW" "'dev-lead'" "per-repo 'dev-lead' fallback group" + # Must NOT collapse back into a single repo-wide group (the #278 regression). + check_absent "$WORKFLOW" "'dev-lead'" "no repo-wide 'dev-lead' group" -# cancel-in-progress must be false so queued runs aren't dropped -check_present "$WORKFLOW" "cancel-in-progress: false" "cancel-in-progress is false" + # cancel-in-progress must be false so a queued same-lane run waits for the active + # run to finish instead of cancelling it (in-flight pickups always complete). + check_present "$WORKFLOW" "cancel-in-progress: false" "cancel-in-progress is false" +done echo "" if [ "$FAIL" -eq 0 ]; then From b71b74d79e0ec4f54266f49faace06afb8a26732 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 7 Jun 2026 01:29:09 +0000 Subject: [PATCH 2/5] fix(reviews): address review comments [skip ci-relay] --- .../integration/test_concurrency_config.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/dev-lead/integration/test_concurrency_config.sh b/tests/dev-lead/integration/test_concurrency_config.sh index c71ddc1d9..3c89f0cb9 100644 --- a/tests/dev-lead/integration/test_concurrency_config.sh +++ b/tests/dev-lead/integration/test_concurrency_config.sh @@ -43,17 +43,27 @@ for WORKFLOW in \ ".github/workflows/dev-lead.yml" \ ".github/workflows/dev-lead-reusable.yml"; do echo "── $WORKFLOW ──" + if [ ! -f "$WORKFLOW" ]; then + echo "FAIL: $WORKFLOW does not exist. Please run this script from the repository root." + FAIL=$((FAIL + 1)) + continue + fi # Per-issue and per-PR lanes must be present so a labeled-issue pickup is never # cancelled by unrelated PR follow-up traffic. - check_present "$WORKFLOW" "dev-lead-pr-" "per-PR lane" - check_present "$WORKFLOW" "dev-lead-issue-" "per-issue lane" + # Match format('dev-lead-…') to target the concurrency expression, not comments. + check_present "$WORKFLOW" "format('dev-lead-pr-" "per-PR lane" + check_present "$WORKFLOW" "format('dev-lead-issue-" "per-issue lane" # ci-relay keeps its own ephemeral per-SHA slot so it fires immediately. - check_present "$WORKFLOW" "dev-lead-ci-relay-" "ci-relay per-SHA lane" + check_present "$WORKFLOW" "format('dev-lead-ci-relay-" "ci-relay per-SHA lane" # Must NOT collapse back into a single repo-wide group (the #278 regression). - check_absent "$WORKFLOW" "'dev-lead'" "no repo-wide 'dev-lead' group" + # Guard all common bare-value spellings so a regression can't slip through + # on quote-style changes. + check_absent "$WORKFLOW" "group: dev-lead" "no bare unquoted group: dev-lead" + check_absent "$WORKFLOW" "group: 'dev-lead'" "no bare single-quoted group: dev-lead" + check_absent "$WORKFLOW" 'group: "dev-lead"' "no bare double-quoted group: dev-lead" # cancel-in-progress must be false so a queued same-lane run waits for the active # run to finish instead of cancelling it (in-flight pickups always complete). From 45a128f71047f4cb151684f1c7659c94c702ba8f Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sat, 6 Jun 2026 21:47:50 -0500 Subject: [PATCH 3/5] ci(dev-lead): run concurrency-config on dev-lead workflow changes pushed to main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The push-to-main path filter only watched scripts/tests/prompts, so a change to .github/workflows/dev-lead*.yml could land on main without ever running the concurrency-config test — exactly how the per-repo->per-lane mismatch stayed hidden after #450 merged. Add the dev-lead workflow files to the push paths. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test-dev-lead.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-dev-lead.yml b/.github/workflows/test-dev-lead.yml index 2dfb2bdce..9406e45a2 100644 --- a/.github/workflows/test-dev-lead.yml +++ b/.github/workflows/test-dev-lead.yml @@ -17,6 +17,11 @@ on: - 'scripts/lib/**' - 'tests/dev-lead/**' - 'prompts/dev-lead/**' + # Guard the concurrency-config test on workflow changes too — otherwise a + # dev-lead.yml edit can land on main without ever running this suite + # (how the per-repo->per-lane mismatch stayed hidden after #450). + - '.github/workflows/dev-lead*.yml' + - '.github/workflows/test-dev-lead.yml' workflow_dispatch: {} permissions: From d59f9739948c48eb2c65c93d1f18143554a59072 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 7 Jun 2026 05:07:21 +0000 Subject: [PATCH 4/5] fix(reviews): address review comments [skip ci-relay] --- .../integration/test_concurrency_config.sh | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/dev-lead/integration/test_concurrency_config.sh b/tests/dev-lead/integration/test_concurrency_config.sh index 3c89f0cb9..b3f7739a0 100644 --- a/tests/dev-lead/integration/test_concurrency_config.sh +++ b/tests/dev-lead/integration/test_concurrency_config.sh @@ -37,6 +37,22 @@ check_present() { fi } +check_order() { + local file="$1" first="$2" second="$3" label="$4" + local line_first line_second + line_first=$(grep -nF "$first" "$file" | head -1 | cut -d: -f1) + line_second=$(grep -nF "$second" "$file" | head -1 | cut -d: -f1) + if [ -z "$line_first" ] || [ -z "$line_second" ]; then + echo "FAIL [$label]: could not find both patterns in $file" + FAIL=$((FAIL + 1)) + elif [ "$line_first" -lt "$line_second" ]; then + echo "PASS [$label]: correct ordering in $file" + else + echo "FAIL [$label]: '$first' must appear before '$second' in $file (first-match-wins routing)" + FAIL=$((FAIL + 1)) + fi +} + # Both the inline workflow and the reusable (source of truth for all consumer # stubs) must carry the per-lane routing. for WORKFLOW in \ @@ -58,12 +74,35 @@ for WORKFLOW in \ # ci-relay keeps its own ephemeral per-SHA slot so it fires immediately. check_present "$WORKFLOW" "format('dev-lead-ci-relay-" "ci-relay per-SHA lane" + # Assert the actual predicate-to-lane bindings, not just token presence. + # issue_comment on a PR has issue.pull_request set; that predicate must route + # to the PR lane so PR follow-up traffic shares the PR slot with other PR events. + check_present "$WORKFLOW" \ + "github.event.issue.pull_request && format('dev-lead-pr-" \ + "issue_comment-on-PR predicate routes to PR lane" + # repository_dispatch (ci-failure relay) carries client_payload.pr_number. + check_present "$WORKFLOW" \ + "github.event.client_payload.pr_number && format('dev-lead-pr-" \ + "repository_dispatch routes to PR lane via client_payload.pr_number" + # issue.pull_request must appear BEFORE issue.number in the || chain. + # If reordered, an issue_comment on a PR would fall through to the issue lane + # and share a slot with issue pickups, allowing cancellation. + check_order "$WORKFLOW" \ + "github.event.issue.pull_request && format('dev-lead-pr-" \ + "github.event.issue.number && format('dev-lead-issue-" \ + "issue_comment-on-PR predicate before generic issue predicate" + # Must NOT collapse back into a single repo-wide group (the #278 regression). # Guard all common bare-value spellings so a regression can't slip through # on quote-style changes. check_absent "$WORKFLOW" "group: dev-lead" "no bare unquoted group: dev-lead" check_absent "$WORKFLOW" "group: 'dev-lead'" "no bare single-quoted group: dev-lead" check_absent "$WORKFLOW" 'group: "dev-lead"' "no bare double-quoted group: dev-lead" + # Also reject a repo-wide fallback hidden inside the ${{ ... }} expression. + # The group: >- form puts the value on the next lines starting with ||, so + # "group: dev-lead" patterns above would miss a regression like || 'dev-lead'. + check_absent "$WORKFLOW" "|| 'dev-lead'" "no repo-wide || 'dev-lead' fallback in expression" + check_absent "$WORKFLOW" '|| "dev-lead"' 'no repo-wide || "dev-lead" fallback in expression' # cancel-in-progress must be false so a queued same-lane run waits for the active # run to finish instead of cancelling it (in-flight pickups always complete). From 8712c4c9f2758eef37df5562578fb4ba841ff5e5 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 7 Jun 2026 05:13:12 +0000 Subject: [PATCH 5/5] fix(reviews): address review comments [skip ci-relay] --- tests/dev-lead/integration/test_concurrency_config.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/dev-lead/integration/test_concurrency_config.sh b/tests/dev-lead/integration/test_concurrency_config.sh index b3f7739a0..f510464aa 100644 --- a/tests/dev-lead/integration/test_concurrency_config.sh +++ b/tests/dev-lead/integration/test_concurrency_config.sh @@ -84,6 +84,13 @@ for WORKFLOW in \ check_present "$WORKFLOW" \ "github.event.client_payload.pr_number && format('dev-lead-pr-" \ "repository_dispatch routes to PR lane via client_payload.pr_number" + # client_payload.pr_number predicate must appear BEFORE the final run-id fallback. + # If reordered, the fallback is always truthy and every repository_dispatch + # (ci-failure relay) gets a unique run lane instead of sharing the PR lane. + check_order "$WORKFLOW" \ + "github.event.client_payload.pr_number && format('dev-lead-pr-" \ + "format('dev-lead-run-{0}', github.run_id)" \ + "repository_dispatch predicate before run-id fallback" # issue.pull_request must appear BEFORE issue.number in the || chain. # If reordered, an issue_comment on a PR would fall through to the issue lane # and share a slot with issue pickups, allowing cancellation.