ci: skip checks for draft pull requests [skip ci] (#29484)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-15 23:18:13 -07:00
committed by GitHub
co-authored by OrcaWin
parent 3074852b7c
commit fc584d425b
33 changed files with 138 additions and 17 deletions
@@ -18,8 +18,8 @@ export function verifyRequiredPrContext(parseYaml) {
}
const gate = providers[0].job
const condition = gate.if?.replace(/^\$\{\{\s*|\s*\}\}$/g, '').trim()
if (condition !== '!cancelled()') {
throw new Error('Required PR aggregate must run unless the workflow is cancelled.')
if (condition !== '!cancelled() && github.event.pull_request.draft != true') {
throw new Error('Required PR aggregate must run unless cancelled or the PR is a draft.')
}
if (gate['continue-on-error'] || gate.steps?.some((step) => step['continue-on-error'])) {
throw new Error('Required PR aggregate must not ignore failures.')
@@ -45,6 +45,7 @@ on:
- 'config/scripts/windows-rust-executable-build.mjs'
- 'resources/build/icon.ico'
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- '.github/workflows/ci-cache-warmup-platforms.yml'
- '.github/workflows/ci-cache-warmup-platform-jobs.yml'
@@ -58,4 +59,5 @@ concurrency:
jobs:
warm-platforms:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
uses: ./.github/workflows/ci-cache-warmup-platform-jobs.yml
+2
View File
@@ -48,6 +48,7 @@ on:
- 'native/windows-registry/**'
- 'native/windows-security/**'
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- '.github/workflows/ci-cache-warmup.yml'
- '.github/actions/prepare-headless-compiler/**'
@@ -63,6 +64,7 @@ concurrency:
jobs:
warm:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
concurrency:
group: ci-cache-warmup-linux-x64-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
@@ -2,6 +2,7 @@ name: pnpm verification cache pilot
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths: ['.github/workflows/ci-pnpm-verification-pilot.yml']
workflow_dispatch:
@@ -17,6 +18,7 @@ env:
jobs:
seed:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
matrix:
runner: [ubuntu-latest, ubuntu-24.04-arm]
+5
View File
@@ -2,6 +2,7 @@ name: Cloud Verify
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- cloud/**
- src/packages/process-host/**
@@ -29,6 +30,7 @@ defaults:
jobs:
# Public-repository hosted runners preserve Blacksmith allowance for macOS.
security:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: Secret scan
runs-on: ubuntu-22.04
steps:
@@ -79,6 +81,7 @@ jobs:
# Compiles the workspace. No Postgres service: nothing here reaches a
# database, and the service container costs ~13s of startup.
build:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@@ -99,6 +102,7 @@ jobs:
# packages it needs through pretest hooks, so it does not depend on
# `pnpm build` having run.
test:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-22.04
services:
postgres:
@@ -135,6 +139,7 @@ jobs:
# Fork pull requests reach this job, so it never configures a backend, never plans, and never
# holds a credential. Only the relay root ships here; foundation and apps stay private.
terraform:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
+3 -1
View File
@@ -2,6 +2,7 @@ name: Computer-use e2e
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- '.github/workflows/computer-e2e.yml'
- 'config/electron-builder.config.cjs'
@@ -65,7 +66,7 @@ concurrency:
jobs:
native-smoke:
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.draft != true
permissions:
contents: read
strategy:
@@ -141,6 +142,7 @@ jobs:
# Hosted macOS runners cannot receive persistent Accessibility/Screen Recording
# grants. Keep the real native build/owner-loss checks on every trigger instead.
mac-native-owner-smoke:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: macos-15
permissions:
contents: read
+2 -1
View File
@@ -6,6 +6,7 @@ on:
release:
types: [published]
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'docs/site/**'
- '.github/workflows/docs.yml'
@@ -36,7 +37,7 @@ jobs:
# including forks.
check:
name: Build and test
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.draft != true
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
@@ -1,6 +1,7 @@
name: Git command termination runtime
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/git/command-runner/spawned-command-tree-kill*'
- '.github/workflows/git-command-termination-runtime.yml'
@@ -12,6 +13,7 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
windows-exit:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: windows-latest
timeout-minutes: 20
env:
@@ -2,6 +2,7 @@ name: macOS updater regression tests
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- '.github/workflows/macos-updater-tests.yml'
- 'src/main/macos-update-running-instances*'
@@ -24,6 +25,7 @@ concurrency:
jobs:
updater:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: macos-15
timeout-minutes: 20
steps:
+3 -1
View File
@@ -6,6 +6,8 @@ on:
- opened
- synchronize
- reopened
- ready_for_review
- converted_to_draft
paths:
- 'mobile/**'
# Why all of src/shared: mobile imports hundreds of its modules, directly and through the RPC
@@ -94,7 +96,7 @@ concurrency:
jobs:
verify:
name: Mobile verify
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.draft != true
# Why ARM: 209s of this job is Vitest and nothing here needs x86: no Android SDK, emulator, gradle,
# Hermes or Watchman, no docker, and no artifacts. The Gemfile.lock lists the generic `ruby`
# platform, so frozen bundler installs without an aarch64-linux entry.
+1 -1
View File
@@ -2,7 +2,7 @@ name: Headless Node server
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/**'
- 'config/**'
@@ -5,6 +5,7 @@ on:
- cron: '15 9 * * *'
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- '.github/workflows/performance-contracts.yml'
- 'config/vitest.performance.config.ts'
@@ -40,6 +41,7 @@ concurrency:
jobs:
contracts:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
+2
View File
@@ -1,6 +1,7 @@
name: Pi owner runtime verification
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/pi/**'
- '!src/main/pi/**/*.test.ts'
@@ -15,6 +16,7 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
runtime:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
@@ -1,6 +1,7 @@
name: Pi extension provider verification
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/shared/commit-message-agent-specs-primary.ts'
- 'tests/tools/pi-provider-runtime-smoke.mjs'
@@ -12,6 +13,7 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
runtime:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
+3 -1
View File
@@ -8,6 +8,7 @@ on:
- synchronize
- reopened
- ready_for_review
- converted_to_draft
concurrency:
group: pr-checks-${{ github.event.pull_request.number }}
@@ -24,6 +25,7 @@ jobs:
# Per-job outputs also skip git-compat/xterm/packaging/shell when those
# inputs are unchanged; empty diffs fail closed and run everything.
code_paths:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: detect changes and check repository guards
# Start routing without waiting behind the GitHub-hosted test queue.
runs-on: blacksmith-2vcpu-ubuntu-2404
@@ -1515,7 +1517,7 @@ jobs:
verify:
name: PR checks passed
# Publish an explicit result after every required dependency settles.
if: ${{ !cancelled() }}
if: ${{ !cancelled() && github.event.pull_request.draft != true }}
needs:
- code_paths
- preflight
@@ -1,6 +1,7 @@
name: Preflight comparison
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- .github/workflows/preflight-benchmark.yml
- config/scripts/benchmark-preflight-*.mjs
@@ -15,6 +16,7 @@ env:
ORCA_BACKGROUND_LAUNCH: '1'
jobs:
compilers:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-24.04-arm
timeout-minutes: 25
steps:
@@ -37,6 +39,7 @@ jobs:
path: .build/preflight-benchmark/
retention-days: 30
static-analysis:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-24.04-arm
timeout-minutes: 25
steps:
@@ -59,6 +62,7 @@ jobs:
path: .build/preflight-benchmark/
retention-days: 30
compatibility-imports:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
@@ -82,6 +86,7 @@ jobs:
path: .build/preflight-benchmark/
retention-days: 30
localization-extraction:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-24.04-arm
timeout-minutes: 12
steps:
@@ -2,6 +2,7 @@ name: Preflight CLI platforms
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/ipc/preflight*'
- 'src/main/ipc/agent-detection-shell-path.ts'
@@ -22,6 +23,7 @@ concurrency:
jobs:
preflight:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: Preflight CLI (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
@@ -1,6 +1,7 @@
name: Preflight critical-path comparison
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- .github/workflows/preflight-critical-path-benchmark.yml
- config/scripts/benchmark-ci-preflight-critical-path.mjs
@@ -14,6 +15,7 @@ env:
ORCA_BACKGROUND_LAUNCH: '1'
jobs:
compare:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
runs-on: ubuntu-24.04-arm
timeout-minutes: 20
strategy:
@@ -2,6 +2,7 @@ name: Release JavaScript comparison
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths: ['.github/workflows/release-javascript-benchmark.yml']
workflow_dispatch:
@@ -20,6 +21,7 @@ env:
jobs:
bundle:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
uses: ./.github/workflows/release-javascript.yml
with:
ref: ${{ github.sha }}
@@ -2,6 +2,7 @@ name: Release ref validation
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- '.github/workflows/adhoc-mac-build.yml'
- '.github/workflows/dev-channel-win-build.yml'
@@ -19,6 +20,7 @@ concurrency:
jobs:
validate:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
@@ -6,6 +6,7 @@ name: Skill update round trip
# run marks the default-branch tip failed, so the repo shows a red X.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths: &skill-roundtrip-paths
- 'skills/**'
- 'resources/skills/**'
@@ -28,6 +29,7 @@ concurrency:
jobs:
roundtrip:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
+1 -1
View File
@@ -7,7 +7,7 @@ name: SSH hostile hosts
# or the slot build changes, and on demand.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
# The managed deploy still runs on these relay-named install and runtime primitives.
- 'src/main/ssh/ssh-relay-*'
+1 -1
View File
@@ -6,7 +6,7 @@ name: SSH Windows hosts
# sessions behind logging shims, and deploys orcad on the pinned node.exe in both DefaultShell cases.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/ssh/ssh-relay-*'
- 'src/main/ssh/*runtime*'
@@ -8,7 +8,7 @@ name: Windows packaged orcad serve switch E2E
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/daemon/**'
- '!src/main/daemon/**/*.test.ts'
@@ -2,6 +2,7 @@ name: Windows typecheck parity
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'package.json'
- 'pnpm-lock.yaml'
@@ -25,6 +26,7 @@ concurrency:
jobs:
parity:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: Windows compiler diagnostics and file selection
runs-on: windows-2022
timeout-minutes: 40
+2
View File
@@ -7,6 +7,7 @@ name: WSL orcad E2E
# Hosted WSL2 was proven in #27783: WSL 2.7.14, import 13-18 s, cold boot 8-14 s.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/wsl/**'
- '!src/main/wsl/**/*.test.ts'
@@ -38,6 +39,7 @@ env:
jobs:
# The distro is linux-x64-glibc; its node-pty slot only builds on Linux, and nothing else is needed.
template:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: Build the linux-x64-glibc orcad template
runs-on: ubuntu-latest
timeout-minutes: 20
@@ -1,6 +1,7 @@
name: Coding Plan credential contract
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/zcode/**'
- 'src/shared/node-bounded-file-reader*'
@@ -25,6 +26,7 @@ concurrency:
cancel-in-progress: true
jobs:
credential-files:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: ZCode credentials (${{ matrix.os }})
strategy:
fail-fast: false
@@ -1,6 +1,7 @@
name: ZCode transcript contract
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- 'src/main/ai-vault/session-scanner-zcode*'
- 'src/main/ai-vault/session-scanner-opencode*'
@@ -18,6 +19,7 @@ concurrency:
cancel-in-progress: true
jobs:
transcript-readers:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
@@ -0,0 +1,70 @@
import { readFileSync, readdirSync } from 'node:fs'
import { expect, it } from 'vitest'
import { parse } from 'yaml'
import { compileAggregateExpression } from './pr-aggregate-gate-context.mjs'
const workflows = readdirSync('.github/workflows').flatMap((file) => {
const workflow = parse(readFileSync(`.github/workflows/${file}`, 'utf8'))
// The LoC reporter updates PR metadata rather than running CI.
return Object.hasOwn(workflow.on ?? {}, 'pull_request') && file !== 'pr-test-loc.yml'
? [{ file, workflow }]
: []
})
function admitted(condition, draft, needs = {}, eventName = 'pull_request') {
return (
compileAggregateExpression(condition ?? 'true', ['github', 'needs', 'inputs'])({
github: {
event_name: eventName,
repository: 'stablyai/orca',
event: { pull_request: { draft } }
},
needs,
inputs: {}
}).coerceString() === 'true'
)
}
it.each(workflows)('$file starts no CI jobs for a draft PR', ({ workflow }) => {
const needs = Object.fromEntries(
Object.keys(workflow.jobs).map((id) => [id, { result: 'skipped', outputs: {} }])
)
for (const [id, job] of Object.entries(workflow.jobs)) {
// Jobs without a status function inherit success(), so skipped parents block them.
if (job.needs && !/\b(?:cancelled|always|success|failure)\(/.test(job.if ?? '')) {
continue
}
expect(admitted(job.if, true, needs), id).toBe(false)
}
})
it.each(workflows)('$file starts CI when ready and cancels it when drafted', ({ workflow }) => {
const types = workflow.on.pull_request.types
expect(types).toEqual(
expect.arrayContaining([
'opened',
'synchronize',
'reopened',
'ready_for_review',
'converted_to_draft'
])
)
expect(Object.values(workflow.jobs).some((job) => !job.needs && admitted(job.if, false))).toBe(
true
)
const concurrency = workflow.concurrency['cancel-in-progress']
expect(typeof concurrency === 'boolean' ? concurrency : admitted(concurrency, true)).toBe(true)
})
it.each(workflows)('$file keeps its non-PR entry points eligible', ({ workflow }) => {
const needs = Object.fromEntries(
Object.keys(workflow.jobs).map((id) => [id, { result: 'success', outputs: {} }])
)
for (const event of Object.keys(workflow.on).filter((name) => name !== 'pull_request')) {
for (const [id, job] of Object.entries(workflow.jobs)) {
expect(admitted(job.if, true, needs, event), `${event}/${id}`).toBe(
admitted(job.if, false, needs, event)
)
}
}
})
+1 -1
View File
@@ -24,7 +24,7 @@ export function compileAggregateExpression(expression, contexts = ['needs']) {
).evaluate()
}
export const aggregateShouldRun = compileAggregateExpression(aggregateJob.if)
export const aggregateShouldRun = compileAggregateExpression(aggregateJob.if, ['needs', 'github'])
const aggregateEnvironmentExpressions = Object.fromEntries(
Object.entries(aggregateShell.env).map(([name, expression]) => [
name,
+4 -2
View File
@@ -12,7 +12,10 @@ import {
const results = ['success', 'failure', 'cancelled', 'skipped', '', 'unknown', undefined, null]
const admitted = (needs, cancelled = false) =>
aggregateShouldRun({ needs }, cancelled).coerceString() === 'true'
aggregateShouldRun(
{ needs, github: { event: { pull_request: { draft: false } } } },
cancelled
).coerceString() === 'true'
describe('explicit PR aggregate result', () => {
it('keeps every required dependency and the existing failure reporter', () => {
@@ -27,7 +30,6 @@ describe('explicit PR aggregate result', () => {
}
expect(aggregateJob['continue-on-error']).toBeUndefined()
expect(aggregateShell['continue-on-error']).toBeUndefined()
expect(aggregateJob.if).toBe('${{ !cancelled() }}')
expect(aggregateShell.run).toContain('"$CODE_PATHS" != "success"')
const checked = [...aggregateShell.run.matchAll(/^check_job (\S+) /gm)].map((match) => match[1])
expect(checked).toEqual(aggregateRequiredJobs)
+1 -2
View File
@@ -151,7 +151,6 @@ describe('ready-for-review required check reuse', () => {
expect(detector.steps.find((step) => step.id === 'e2e_filter').if).toBe(
"github.event.pull_request.draft != true && steps.filter.outputs.should_run == 'true'"
)
expect(workflow.jobs.verify.if).toBe('${{ !cancelled() }}')
expect(workflow.jobs.verify.needs).toEqual([
'code_paths',
'preflight',
@@ -163,7 +162,7 @@ describe('ready-for-review required check reuse', () => {
])
})
it.each(['pr-test-loc.yml', 'mobile.yml'])(
it.each(['pr-test-loc.yml'])(
'does not rerun draft-independent %s on readiness changes',
(file) => {
const source = readFileSync(`.github/workflows/${file}`, 'utf8')
@@ -22,7 +22,6 @@ it('checks context names even when workflow-only changes skip root tests', () =>
(candidate) => candidate.run === 'bun .github/scripts/check-required-pr-context.mjs'
)
expect(step).toBeDefined()
expect(job.if).toBeUndefined()
expect(step.if).toBeUndefined()
expect(step['continue-on-error']).toBeUndefined()
const checkout = job.steps.find((candidate) => candidate.uses?.startsWith('actions/checkout@'))
@@ -45,7 +44,7 @@ it('rejects aggregate conditions that hide a successful result', () => {
return workflow
}
expect(() => verifyRequiredPrContext(parseChanged), String(condition)).toThrow(
'Required PR aggregate must run unless the workflow is cancelled.'
'Required PR aggregate must run unless cancelled or the PR is a draft.'
)
}
})