Skip to content

Commit 8ba924b

Browse files
committed
ci: gate live-e2e jobs on static-offline via needs: (prevents wasted env-approvals on runs that would fail static)
1 parent 1f99bb2 commit 8ba924b

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

.github/workflows/runtime-live-e2e.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,29 @@ permissions:
4949
pull-requests: read
5050

5151
jobs:
52+
# Gate job: static suite must pass before any live-e2e job burns env-approval.
53+
# Duplicate of the standalone Static CI workflow (ci-static.yml); intentional
54+
# redundancy lets the live-e2e jobs `needs:` it natively without cross-workflow
55+
# orchestration. The standalone workflow stays for the top-level PR status card.
56+
static-offline:
57+
runs-on: ubuntu-latest
58+
steps:
59+
- uses: actions/checkout@v4
60+
with:
61+
ref: ${{ github.event.pull_request.head.sha }}
62+
persist-credentials: false
63+
64+
- uses: actions/setup-python@v5
65+
with:
66+
python-version: "3.12"
67+
68+
- uses: astral-sh/setup-uv@v6
69+
70+
- name: Run offline static suite
71+
run: make test-static
72+
5273
claude-live:
74+
needs: static-offline
5375
runs-on: ubuntu-latest
5476
environment:
5577
name: CI-E2E
@@ -194,6 +216,7 @@ jobs:
194216
if-no-files-found: warn
195217

196218
claude-live-bare:
219+
needs: static-offline
197220
runs-on: ubuntu-latest
198221
environment:
199222
name: CI-E2E
@@ -338,6 +361,7 @@ jobs:
338361
if-no-files-found: warn
339362

340363
claude-live-opus:
364+
needs: static-offline
341365
runs-on: ubuntu-latest
342366
environment:
343367
name: CI-E2E-OPUS
@@ -490,6 +514,7 @@ jobs:
490514
if-no-files-found: warn
491515

492516
codex-live:
517+
needs: static-offline
493518
runs-on: ubuntu-latest
494519
environment:
495520
name: CI-E2E-CODEX

tests/test_runtime_live_e2e_workflow.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,19 @@ def test_runtime_live_e2e_workflow_supports_pr_and_manual_triggers():
7070

7171

7272
def test_runtime_live_e2e_workflow_checks_out_pr_head_with_persist_credentials_false():
73-
"""Security-critical: every live-e2e checkout must target the PR head SHA
74-
and MUST set persist-credentials: false so GITHUB_TOKEN is not persisted
75-
into .git/config where post-checkout steps could use it."""
73+
"""Security-critical: every checkout in this workflow must target the PR head
74+
SHA and MUST set persist-credentials: false so GITHUB_TOKEN is not persisted
75+
into .git/config where post-checkout steps could use it.
76+
77+
Five checkouts in current shape: 4 live-e2e jobs (claude-live, claude-live-bare,
78+
claude-live-opus, codex-live) + 1 static-offline gate job. All five must
79+
satisfy the head-SHA + persist-credentials invariant."""
7680
text = read_workflow()
7781

7882
checkout_count = text.count("actions/checkout@v4")
7983
head_ref_count = text.count("ref: ${{ github.event.pull_request.head.sha }}")
8084
# Each checkout must have a matching head-SHA ref.
81-
assert checkout_count == 4
85+
assert checkout_count == 5
8286
assert head_ref_count == checkout_count, (
8387
f"Every checkout@v4 must pin to the PR head SHA "
8488
f"(found {checkout_count} checkouts, {head_ref_count} head-SHA refs)"
@@ -92,6 +96,23 @@ def test_runtime_live_e2e_workflow_checks_out_pr_head_with_persist_credentials_f
9296
)
9397

9498

99+
def test_runtime_live_e2e_workflow_gates_live_jobs_on_static_offline():
100+
"""Live-e2e jobs MUST NOT run if static-offline fails. Captain env-approval
101+
is expensive; gating on static-offline prevents wasted approvals on runs
102+
that would fail on static checks anyway."""
103+
text = read_workflow()
104+
105+
# The gate job must exist inline (not only in the standalone ci-static.yml).
106+
assert " static-offline:\n" in text, (
107+
"Expected a static-offline job in runtime-live-e2e.yml as the needs: target"
108+
)
109+
# All 4 live jobs must declare needs: static-offline.
110+
needs_count = text.count("needs: static-offline")
111+
assert needs_count == 4, (
112+
f"Expected all 4 live-e2e jobs to declare 'needs: static-offline', got {needs_count}"
113+
)
114+
115+
95116
def test_runtime_live_e2e_workflow_narrows_default_permissions():
96117
text = read_workflow()
97118

0 commit comments

Comments
 (0)