Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 || github.repository != 'ContextualWisdomLab/.github'))
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
Expand Down Expand Up @@ -184,9 +184,14 @@ 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 ||
github.repository != 'ContextualWisdomLab/.github'))
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
Expand Down Expand Up @@ -241,9 +246,15 @@ 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 ||
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
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.d/20260925-strix-draft-admission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Strix skips Draft pull-request admission

- 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.
- The synchronous model job remains intentionally unbounded under the
repository's progress-based Strix occupancy policy.
52 changes: 52 additions & 0 deletions docs/doctoring/strix-draft-admission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Strix Draft pull-request admission

## Decision

`.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
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 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

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. 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.
36 changes: 36 additions & 0 deletions tests/test_required_workflow_queue_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,42 @@ def test_review_workflow_completions_do_not_spawn_scheduler_runs() -> None:
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")

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.repository != 'ContextualWisdomLab/.github'" 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.repository != 'ContextualWisdomLab/.github'" 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_required_workflow_trusted_source_refs_are_not_input_controlled() -> None:
"""Ensure privileged workflows resolve trusted source code independently of inputs."""
for filename in (
Expand Down
Loading