From 0ab377a5f667427f832a98fbb2f7053d8213e971 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 25 Sep 2026 15:43:40 +0000 Subject: [PATCH 1/3] fix(strix): skip Draft pull-request admission Co-authored-by: Seongho Bae --- .github/workflows/strix.yml | 15 ++++-- CHANGELOG.d/20260925-strix-draft-admission.md | 9 ++++ docs/doctoring/strix-draft-admission.md | 48 +++++++++++++++++++ .../test_required_workflow_queue_contract.py | 35 ++++++++++++++ 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.d/20260925-strix-draft-admission.md create mode 100644 docs/doctoring/strix-draft-admission.md diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index f15b29f564..a9cf0b101d 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -129,7 +129,7 @@ jobs: # here and consumed through `needs`. See # docs/doctoring/required-workflow-path-filter-boundary.md. # Fails OPEN: an unreadable, empty, or truncated file list scans everything. - if: github.event_name != 'pull_request_target' || (github.event.action != 'closed' && github.event.action != 'converted_to_draft') + if: github.event_name != 'pull_request_target' || (github.event.action != 'closed' && github.event.action != 'converted_to_draft' && github.event.pull_request.draft == false) runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -184,9 +184,13 @@ jobs: admit-current-head: name: Admit current pull request head + # Keep exact-head admission for every non-Draft PR while leaving push, + # schedule, and repository_dispatch paths unchanged. if: >- github.event_name != 'pull_request_target' || - (github.event.action != 'closed' && github.event.action != 'converted_to_draft') + (github.event.action != 'closed' && + github.event.action != 'converted_to_draft' && + github.event.pull_request.draft == false) runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -241,9 +245,14 @@ jobs: } >> "$GITHUB_OUTPUT" cancel-superseded-pr-runs: + # A converted_to_draft run is intentionally runner-free: workflow-level + # cancel-in-progress retires the prior Ready generation before admission. + # Keep the bounded API cleanup for non-Draft synchronize and closed events. if: >- github.event_name == 'pull_request_target' && - (github.event.action == 'synchronize' || github.event.action == 'converted_to_draft' || github.event.action == 'closed') + (github.event.action == 'closed' || + (github.event.action == 'synchronize' && + github.event.pull_request.draft == false)) # Idempotent per PR: a fresh sweep re-verifies live state (live_target_matches # below) before selecting or cancelling anything, so it fully subsumes # whatever an older, not-yet-run instance would have done. cancel-in-progress diff --git a/CHANGELOG.d/20260925-strix-draft-admission.md b/CHANGELOG.d/20260925-strix-draft-admission.md new file mode 100644 index 0000000000..9573114268 --- /dev/null +++ b/CHANGELOG.d/20260925-strix-draft-admission.md @@ -0,0 +1,9 @@ +### Strix skips Draft pull-request admission + +- Draft `pull_request_target` generations now skip Strix metadata and scanning + jobs without a runner; `ready_for_review` creates the fresh exact-head scan. +- Non-Draft PR pushes, forced `repository_dispatch`, push, and scheduled scans + remain admitted. `converted_to_draft` still cancels the prior Ready + generation through workflow concurrency without admitting a cleanup runner. +- The synchronous model job remains intentionally unbounded under the + repository's progress-based Strix occupancy policy. diff --git a/docs/doctoring/strix-draft-admission.md b/docs/doctoring/strix-draft-admission.md new file mode 100644 index 0000000000..24f39c54b0 --- /dev/null +++ b/docs/doctoring/strix-draft-admission.md @@ -0,0 +1,48 @@ +# Strix Draft pull-request admission + +## Decision + +`.github/workflows/strix.yml` now evaluates Draft state at job level for +`pull_request_target` events. Draft `opened`, `synchronize`, and `reopened` +generations skip both metadata jobs, so the dependent `strix` job is skipped +without a runner. `ready_for_review` remains in the trigger types and carries +`draft == false`, so it admits a fresh exact-head metadata generation and then +the real scan. Non-PR `push`, `schedule`, and `repository_dispatch` events +remain admitted. + +`converted_to_draft` remains a trigger event so workflow-level +`cancel-in-progress` retires an older Ready generation. Its replacement is +runner-free: the explicit API cleanup job is not admitted for that event. +Closed PRs and non-Draft synchronize events retain the existing cleanup path, +including its live-target and superseded-run checks. + +This preserves the required `strix` check shape for non-Draft PRs and forced +repository-dispatch scans. A Draft run produces skipped job conclusions rather +than leaving an uncreated trigger-level check Pending; Draft PRs cannot merge, +and the Ready transition creates the exact-head scan that can satisfy the +required context. + +## Timeout decision + +The `strix` job still has no job-level `timeout-minutes`. This was verified +against the workflow and the existing timeout contracts. The job runs the +model synchronously and explicitly sets `LLM_TIMEOUT`, +`STRIX_MEMORY_COMPRESSOR_TIMEOUT`, `STRIX_PROCESS_TIMEOUT_SECONDS`, and +`STRIX_TOTAL_TIMEOUT_SECONDS` to zero. The repository's standing model-path +policy accepts central Strix work taking more than two hours and rejects +elapsed inference caps; the active Strix occupancy record therefore uses +progress-based transport release rather than a wall-clock job deadline. + +Adding a job timeout here would terminate legitimate large-repository analysis +and contradict that policy. The short job-level limits on `changed-scope`, +`admit-current-head`, and the superseded-run cleanup remain because those jobs +perform bounded metadata/API work, not model inference. + +## Scope and follow-up + +This repair changes only Strix Draft admission and documents the timeout +decision. The other heavy required PR workflows should receive the same +runner-free Draft/`ready_for_review` lifecycle treatment in a separate, +coordinated change: Security Scan, SAST Semgrep, CodeQL PR, Python Security, +and Agent Review Runtime Quality. No metadata-job consolidation is included; +that remains a separate queue-reduction concern. diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 87277d45f5..99ec6cfe68 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -1142,6 +1142,41 @@ def test_pull_request_close_events_cancel_superseded_runs_without_heavy_jobs() - assert workflow_level_cancels_in_progress(strix_workflow) +def test_strix_draft_pr_events_skip_runner_admission_until_ready() -> None: + """Draft PR generations skip every Strix runner job until review admission.""" + workflow = workflow_text("strix.yml") + + def job_block(job_name: str) -> str: + match = re.search( + rf"(?ms)^ {re.escape(job_name)}:\n(.*?)(?=^ [A-Za-z0-9_-]+:\s*$|\Z)", + workflow, + ) + assert match is not None, job_name + return match.group(1) + + for job_name in ("changed-scope", "admit-current-head"): + block = job_block(job_name) + assert "github.event.pull_request.draft == false" in block + assert "github.event_name != 'pull_request_target'" in block + + cleanup_block = job_block("cancel-superseded-pr-runs") + cleanup_if_start = cleanup_block.index(" if:") + cleanup_header = cleanup_block[ + cleanup_if_start : cleanup_block.index(" runs-on:", cleanup_if_start) + ] + assert "github.event.action == 'closed'" in cleanup_header + assert "github.event.action == 'synchronize'" in cleanup_header + assert "github.event.pull_request.draft == false" in cleanup_header + assert "github.event.action == 'converted_to_draft'" not in cleanup_header + + strix = job_block("strix") + assert "needs: [changed-scope, admit-current-head]" in strix + assert "needs.changed-scope.outputs.code == 'true'" in strix + assert "needs.admit-current-head.outputs.admitted == 'true'" in strix + assert "ready_for_review" in workflow + assert "converted_to_draft" in workflow + + def test_merge_scheduler_owns_empty_pr_cleanup_without_checkout() -> None: """Keep empty-PR cleanup in the existing metadata-only scheduler job.""" workflow = workflow_text("pr-review-merge-scheduler.yml") From 236ddff323fb5f7d8a4b5b8ca40adc6c2eff83fd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 25 Sep 2026 16:35:49 +0000 Subject: [PATCH 2/3] fix(strix): scope Draft skip to native runs Co-authored-by: Seongho Bae --- .github/workflows/strix.yml | 8 +++-- CHANGELOG.d/20260925-strix-draft-admission.md | 7 ++-- docs/doctoring/strix-draft-admission.md | 36 ++++++++++--------- .../test_required_workflow_queue_contract.py | 2 ++ 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index a9cf0b101d..4ee8c668a9 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -129,7 +129,7 @@ jobs: # here and consumed through `needs`. See # docs/doctoring/required-workflow-path-filter-boundary.md. # Fails OPEN: an unreadable, empty, or truncated file list scans everything. - if: github.event_name != 'pull_request_target' || (github.event.action != 'closed' && github.event.action != 'converted_to_draft' && github.event.pull_request.draft == false) + if: github.event_name != 'pull_request_target' || (github.event.action != 'closed' && github.event.action != 'converted_to_draft' && (github.event.pull_request.draft == false || github.repository != 'ContextualWisdomLab/.github')) runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -190,7 +190,8 @@ jobs: github.event_name != 'pull_request_target' || (github.event.action != 'closed' && github.event.action != 'converted_to_draft' && - github.event.pull_request.draft == false) + (github.event.pull_request.draft == false || + github.repository != 'ContextualWisdomLab/.github')) runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -252,7 +253,8 @@ jobs: github.event_name == 'pull_request_target' && (github.event.action == 'closed' || (github.event.action == 'synchronize' && - github.event.pull_request.draft == false)) + (github.event.pull_request.draft == false || + github.repository != 'ContextualWisdomLab/.github'))) # Idempotent per PR: a fresh sweep re-verifies live state (live_target_matches # below) before selecting or cancelling anything, so it fully subsumes # whatever an older, not-yet-run instance would have done. cancel-in-progress diff --git a/CHANGELOG.d/20260925-strix-draft-admission.md b/CHANGELOG.d/20260925-strix-draft-admission.md index 9573114268..5e4d3285a2 100644 --- a/CHANGELOG.d/20260925-strix-draft-admission.md +++ b/CHANGELOG.d/20260925-strix-draft-admission.md @@ -1,7 +1,10 @@ ### Strix skips Draft pull-request admission -- Draft `pull_request_target` generations now skip Strix metadata and scanning - jobs without a runner; `ready_for_review` creates the fresh exact-head scan. +- Native `.github` Draft `pull_request_target` generations now skip Strix + metadata and scanning jobs without a runner; ruleset-launched runs in other + repositories always admit them because those runs do not re-trigger on + `ready_for_review`. Native `ready_for_review` creates the fresh exact-head + scan. - Non-Draft PR pushes, forced `repository_dispatch`, push, and scheduled scans remain admitted. `converted_to_draft` still cancels the prior Ready generation through workflow concurrency without admitting a cleanup runner. diff --git a/docs/doctoring/strix-draft-admission.md b/docs/doctoring/strix-draft-admission.md index 24f39c54b0..24e6657c10 100644 --- a/docs/doctoring/strix-draft-admission.md +++ b/docs/doctoring/strix-draft-admission.md @@ -2,13 +2,17 @@ ## Decision -`.github/workflows/strix.yml` now evaluates Draft state at job level for -`pull_request_target` events. Draft `opened`, `synchronize`, and `reopened` -generations skip both metadata jobs, so the dependent `strix` job is skipped -without a runner. `ready_for_review` remains in the trigger types and carries -`draft == false`, so it admits a fresh exact-head metadata generation and then -the real scan. Non-PR `push`, `schedule`, and `repository_dispatch` events -remain admitted. +`.github/workflows/strix.yml` evaluates Draft state at job level only for +native runs in `ContextualWisdomLab/.github`. Draft `opened`, `synchronize`, +and `reopened` generations there skip both metadata jobs, so the dependent +`strix` job is skipped without a runner. `ready_for_review` remains in the +trigger types and carries `draft == false`, so it admits a fresh exact-head +metadata generation and then the real scan. In ruleset-covered repositories, +the repository guard always admits the metadata jobs: ruleset-launched runs +ignore `types` and do not re-trigger on `ready_for_review`, so skipping there +would leave a Draft PR with no later required scan. + +Non-PR `push`, `schedule`, and `repository_dispatch` events remain admitted. `converted_to_draft` remains a trigger event so workflow-level `cancel-in-progress` retires an older Ready generation. Its replacement is @@ -17,10 +21,10 @@ Closed PRs and non-Draft synchronize events retain the existing cleanup path, including its live-target and superseded-run checks. This preserves the required `strix` check shape for non-Draft PRs and forced -repository-dispatch scans. A Draft run produces skipped job conclusions rather -than leaving an uncreated trigger-level check Pending; Draft PRs cannot merge, -and the Ready transition creates the exact-head scan that can satisfy the -required context. +repository-dispatch scans. A native `.github` Draft run produces skipped job +conclusions rather than leaving an uncreated trigger-level check Pending; +ruleset-targeted repositories continue to run the required scan even while +Draft because their workflow cannot depend on `ready_for_review` re-entry. ## Timeout decision @@ -41,8 +45,8 @@ perform bounded metadata/API work, not model inference. ## Scope and follow-up This repair changes only Strix Draft admission and documents the timeout -decision. The other heavy required PR workflows should receive the same -runner-free Draft/`ready_for_review` lifecycle treatment in a separate, -coordinated change: Security Scan, SAST Semgrep, CodeQL PR, Python Security, -and Agent Review Runtime Quality. No metadata-job consolidation is included; -that remains a separate queue-reduction concern. +decision. Security Scan, SAST Semgrep, and CodeQL use the same +`.github`-only Draft boundary in the coordinated follow-up; Python Security +and Agent Review Runtime Quality are not ruleset-required and retain their +direct-run Draft guards. No metadata-job consolidation is included; that +remains a separate queue-reduction concern. diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 99ec6cfe68..1d72c8d5da 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -1157,6 +1157,7 @@ def job_block(job_name: str) -> str: for job_name in ("changed-scope", "admit-current-head"): block = job_block(job_name) assert "github.event.pull_request.draft == false" in block + assert "github.repository != 'ContextualWisdomLab/.github'" in block assert "github.event_name != 'pull_request_target'" in block cleanup_block = job_block("cancel-superseded-pr-runs") @@ -1167,6 +1168,7 @@ def job_block(job_name: str) -> str: assert "github.event.action == 'closed'" in cleanup_header assert "github.event.action == 'synchronize'" in cleanup_header assert "github.event.pull_request.draft == false" in cleanup_header + assert "github.repository != 'ContextualWisdomLab/.github'" in cleanup_header assert "github.event.action == 'converted_to_draft'" not in cleanup_header strix = job_block("strix") From 697fca7e8a349d2fa24817656a65debf3061965a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 25 Sep 2026 16:40:54 +0000 Subject: [PATCH 3/3] test(strix): isolate native admission contract Co-authored-by: Seongho Bae --- .../test_required_workflow_queue_contract.py | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 1d72c8d5da..7bda18c76d 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -1142,6 +1142,22 @@ def test_pull_request_close_events_cancel_superseded_runs_without_heavy_jobs() - assert workflow_level_cancels_in_progress(strix_workflow) +def test_merge_scheduler_owns_empty_pr_cleanup_without_checkout() -> None: + """Keep empty-PR cleanup in the existing metadata-only scheduler job.""" + workflow = workflow_text("pr-review-merge-scheduler.yml") + scheduler = workflow_step(workflow, "Inspect PR review and merge queue") + + assert not (REPO_ROOT / ".github/workflows/close-empty-pr.yml").exists() + assert "pr_review_merge_scheduler.py" in scheduler + assert "actions/checkout" not in workflow + + +def test_review_workflow_completions_do_not_spawn_scheduler_runs() -> None: + """Required checks rely on GitHub auto-merge instead of a follow-up workflow.""" + workflow = workflow_text("pr-review-merge-scheduler.yml") + assert "github.event.workflow_run" not in workflow + + def test_strix_draft_pr_events_skip_runner_admission_until_ready() -> None: """Draft PR generations skip every Strix runner job until review admission.""" workflow = workflow_text("strix.yml") @@ -1178,23 +1194,6 @@ def job_block(job_name: str) -> str: assert "ready_for_review" in workflow assert "converted_to_draft" in workflow - -def test_merge_scheduler_owns_empty_pr_cleanup_without_checkout() -> None: - """Keep empty-PR cleanup in the existing metadata-only scheduler job.""" - workflow = workflow_text("pr-review-merge-scheduler.yml") - scheduler = workflow_step(workflow, "Inspect PR review and merge queue") - - assert not (REPO_ROOT / ".github/workflows/close-empty-pr.yml").exists() - assert "pr_review_merge_scheduler.py" in scheduler - assert "actions/checkout" not in workflow - - -def test_review_workflow_completions_do_not_spawn_scheduler_runs() -> None: - """Required checks rely on GitHub auto-merge instead of a follow-up workflow.""" - workflow = workflow_text("pr-review-merge-scheduler.yml") - assert "github.event.workflow_run" not in workflow - - def test_required_workflow_trusted_source_refs_are_not_input_controlled() -> None: """Ensure privileged workflows resolve trusted source code independently of inputs.""" for filename in (