fix(webdriver): give cloud session creation its own budget and stop leaking billed sessions #6194
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| paths-ignore: | |
| - 'docs/**' | |
| - 'website/**' | |
| - 'README.md' | |
| - '.github/actions/build-docs/action.yml' | |
| - '.github/workflows/deploy.yml' | |
| - '.github/workflows/pr-preview.yml' | |
| - '.github/workflows/pr-preview-cleanup.yml' | |
| push: | |
| branches: | |
| - main | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Two single-`rg`-assertion jobs (formerly `ios-runner-swift-compat`, | |
| # `no-test-di-seams`, added independently in b79bd8601 / 9eb060406) folded | |
| # into steps here: each was checkout + one grep, paying full job | |
| # scheduling/checkout overhead and its own PR status-check line for what is | |
| # a single assertion. Each step keeps its own failure message. See #1462. | |
| static-checks: | |
| name: Static Checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Disallow trailing commas before closing parenthesis in Swift | |
| run: | | |
| if rg -nU --glob '*.swift' ',\s*\n\s*\)' apple/runner; then | |
| echo "Found trailing commas before ')' in Swift files. This syntax requires Swift 6.1+ and breaks older Xcode toolchains." | |
| exit 1 | |
| fi | |
| - name: Fail if test-only DI seams reappear in production code | |
| run: | | |
| if rg '\?\s*:\s*typeof\s+' src/ --glob '!**/__tests__/**' --glob '!*.test.ts'; then | |
| echo "Found test-only DI seams (optional typeof params) in production code." | |
| exit 1 | |
| fi | |
| swift-runner-unit-compile: | |
| name: Swift Runner Unit Compile | |
| runs-on: macos-26 | |
| timeout-minutes: 20 | |
| # Was an inline `VAR=1 pnpm gate …` prefix on the action's build-command input. The | |
| # input is a gate id now, so the variable lives where ios.yml already puts it: the job. | |
| env: | |
| AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS: '1' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Restore and compile Swift runner unit-test surface | |
| uses: ./.github/actions/setup-apple-runner-build | |
| with: | |
| derived-path: ${{ github.workspace }}/.tmp/swift-runner-unit-derived | |
| cache-key-prefix: swift-runner-unit | |
| gate: swift-runner-macos | |
| xcuitest-platform: macos | |
| xcuitest-destination: platform=macOS,arch=arm64 | |
| lint: | |
| name: Lint & Format | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Run oxlint | |
| uses: ./.github/actions/run-gate | |
| with: { gate: lint } | |
| - name: Check formatting | |
| uses: ./.github/actions/run-gate | |
| with: { gate: format } | |
| layering-guard: | |
| name: Layering Guard | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| # This job used to run with install-deps: false, and R8 still holds every | |
| # remaining zero-dep job to that contract. The layering guard itself opted out | |
| # when R7 (SessionState ownership) started parsing the daemon with `oxc-parser` | |
| # instead of matching assignment operators with a regex: a regex cannot see | |
| # `??=` or a computed `session[key] =` write, so the choice was a real parser or | |
| # a rule with holes in it. Keep install-deps enabled. | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Check import-direction DAG | |
| # Generalizes the former inline commands/-import grep into a structured | |
| # import-direction lint over the resolved graph. See scripts/layering/check.ts | |
| # and CONTEXT.md (Architecture: folder DAG + layering lint). | |
| uses: ./.github/actions/run-gate | |
| with: { gate: layering } | |
| - name: Check the depgraph report agrees with the gate | |
| # scripts/depgraph reads the same model as the gate, so its inversion count must | |
| # reproduce TYPE_INVERSION_BASELINE. Free two-sources check: if the tree changes | |
| # and only one side is updated, this fails and names the difference. Runs here | |
| # rather than in its own job so the two can never be green independently. | |
| uses: ./.github/actions/run-gate | |
| with: { gate: depgraph } | |
| # Tests for the TMPDIR redirection itself, hidden the same way as the check above. | |
| # | |
| # Deliberately NOT in Coverage next to `check:tmpdir-leaks`, where the subject matter | |
| # would put it: vitest-tmpdir-global-setup.test.ts proves the lifecycle by spawning a | |
| # real nested `vitest run`, and the Coverage lane already loses runs to | |
| # `[vitest-pool]: Worker forks emitted error` when a fork is slow to terminate. | |
| # Starting a nested Vitest seconds before the full instrumented suite is a contention | |
| # risk with nothing to gain. | |
| - name: Check the tmpdir redirection model | |
| uses: ./.github/actions/run-gate | |
| with: { gate: tmpdir-leaks-model } | |
| affected-selector: | |
| name: Affected-check Selector | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| # The selector's entry closure reaches `@agent-device/kernel` workspace | |
| # specifiers through src/utils/exec.ts and diagnostics.ts (#1490 W0), and | |
| # workspace package resolution needs the pnpm link in node_modules. The | |
| # R8 relative-import exception is reserved for scripts, not production | |
| # src files, so this job installs dependencies instead. | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| # The selector is fail-open and advisory (GitHub CI stays authoritative), | |
| # so the gate only guards the derivation model. | |
| - name: Check affected-selector model | |
| uses: ./.github/actions/run-gate | |
| with: { gate: affected-selector } | |
| # The gate-of-gates (#1429). It shares this job because it validates the | |
| # same artifact the selector is built on — CHECK_CATALOG's `ciJobs` — and | |
| # because a gate that proves the other gates are wired must not be the one | |
| # gate sitting in its own job, green on its own. Deterministic and | |
| # network-free: every input is a file in the checkout. | |
| - name: Check the gate manifest model | |
| uses: ./.github/actions/run-gate | |
| with: { gate: gate-manifest-model } | |
| - name: Check every gate is owned, wired, and reachable | |
| uses: ./.github/actions/run-gate | |
| with: { gate: gate-manifest } | |
| maestro-conformance: | |
| name: Maestro Conformance Oracle | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| # Unlike the layering/affected guards, this job DOES install deps: the | |
| # verifier parses corpus flows with the live engine, and the Maestro parser | |
| # imports the `yaml` package. Keep install-deps enabled. | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| # Layers 1-2 of the conformance oracle: replay the JVM-generated fixtures | |
| # against the live engine. Deterministic and Java-free — the generated | |
| # fixtures are checked in and only regenerated on an upstream-pin bump. The | |
| # device-backed layer 3 runs on the scheduled conformance-differential | |
| # workflow. See scripts/maestro-conformance/README.md. | |
| - name: Verify Maestro conformance fixtures | |
| uses: ./.github/actions/run-gate | |
| with: { gate: maestro-conformance } | |
| packaged-cli-node-22-12: | |
| name: Packaged CLI Node 22.12 | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup build toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Build CLI | |
| uses: ./.github/actions/run-gate | |
| with: { gate: build } | |
| - name: Verify emitted chunk ownership | |
| uses: ./.github/actions/run-gate | |
| with: { gate: bundle-owner-files } | |
| # The build runs on the default toolchain Node and the package is verified on the minimum | |
| # supported Node, so this job covers what a user on `engines.node` floor actually installs. | |
| - name: Setup Node.js 22.12 | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: '22.12' | |
| # Packs, lints the tarball with publint/attw, installs it outside the workspace, and imports | |
| # every published entry point before running the CLI. See scripts/check-package.ts. | |
| # | |
| # Runs the script directly rather than through `pnpm check:package`: the repo's pinned pnpm | |
| # requires Node >= 22.13 and refuses to start on the 22.12 floor this job exists to cover. The | |
| # gate itself only needs `node` and `npm`, so it is the package.json script minus the launcher. | |
| - name: Verify the published package on Node.js 22.12 | |
| run: node --experimental-strip-types scripts/check-package.ts | |
| fallow: | |
| name: Fallow Code Quality | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Run Fallow audit | |
| env: | |
| FALLOW_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| uses: ./.github/actions/run-gate | |
| with: | |
| gate: fallow | |
| args: | | |
| --base | |
| ${{ env.FALLOW_BASE }} | |
| - name: Check for production-unused exports | |
| uses: ./.github/actions/run-gate | |
| with: { gate: production-exports } | |
| replay-compat-provenance: | |
| # The frozen replay-compat corpus (#1417) claims each entry was published by | |
| # a released tag. Only a full-history checkout can re-derive that claim, so | |
| # this job exists separately from the shallow-clone-safe unit lane. | |
| name: Replay-Compat Provenance | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Verify corpus entries against their released blobs | |
| uses: ./.github/actions/run-gate | |
| with: { gate: replay-compat } | |
| released-surface-compat: | |
| # The daemon RPC wire ledger (#1432) is compared against the ledger as it | |
| # stood at the last RELEASED tag, which only a full-history checkout can | |
| # read. Same split as the replay-compat corpus above: the shallow unit lane | |
| # holds the ledger to its source, this job holds it to the last release. | |
| name: Released-Surface Compatibility | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Verify the wire-compat rules | |
| uses: ./.github/actions/run-gate | |
| with: { gate: wire-compat-model } | |
| - name: Compare the daemon RPC wire surface against the last released tag | |
| uses: ./.github/actions/run-gate | |
| with: { gate: daemon-wire-compat } | |
| coverage: | |
| # Runs the full unit + provider-integration suites under coverage with | |
| # thresholds, so a separate unit-tests job would rerun the same tests. | |
| name: Coverage | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Test changed-line coverage gate | |
| uses: ./.github/actions/run-gate | |
| with: { gate: coverage-model } | |
| # The retry list is an enumerated set of owned waivers, so an expired entry | |
| # must fail before the suite runs rather than quietly keeping its retry. | |
| - name: Check contention retry policy | |
| uses: ./.github/actions/run-gate | |
| with: { gate: contention-retry } | |
| # Wrapped in the single-retry policy (#1419): a timeout-shaped failure in | |
| # an enumerated contention-flaky file reruns that file once and reports it | |
| # in the job summary. Assertion failures fail here on the first run. | |
| - name: Run coverage | |
| env: | |
| OUTPUT_ECONOMY_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| uses: ./.github/actions/run-gate | |
| with: { gate: unit-ci } | |
| # The TMPDIR redirection both test lanes depend on (#1593/#1595). The check is a real | |
| # package script that no workflow ran: it is reachable only through `check:unit`, an | |
| # aggregate CI never invokes, so a leak regression could not fail a PR. Placed here | |
| # because this is the lane whose instrumented suite would leak a run directory. | |
| - name: Check for leaked temp directories | |
| uses: ./.github/actions/run-gate | |
| with: { gate: tmpdir-leaks } | |
| - name: Upload contention-retry envelope | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: contention-retry-envelope | |
| if-no-files-found: ignore | |
| path: .tmp/contention-retry/lane-envelope.json | |
| # Reuses the lcov the coverage step just wrote (never runs coverage twice) | |
| # and fails when changed-line coverage < the threshold in | |
| # scripts/coverage-changed/model.ts. The `coverage-waiver` PR label maps to | |
| # the waiver env, which skips the failure but still prints the numbers. | |
| - name: Enforce changed-line coverage gate | |
| if: always() && github.event_name == 'pull_request' | |
| env: | |
| AGENT_DEVICE_COVERAGE_WAIVER: ${{ contains(github.event.pull_request.labels.*.name, 'coverage-waiver') }} | |
| uses: ./.github/actions/run-gate | |
| with: | |
| gate: coverage | |
| args: | | |
| --base | |
| ${{ github.event.pull_request.base.sha }} | |
| typecheck: | |
| name: Typecheck | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Run typecheck | |
| uses: ./.github/actions/run-gate | |
| with: { gate: typecheck } | |
| # CHECK_CATALOG has always claimed this job mirrors the `mcp-metadata` | |
| # check; until #1429 checked the claim, no PR job ran it at all, so | |
| # server.json/smithery.yaml drift only surfaced at publish time (where | |
| # publish-mcp-registry.yml duplicates the same command). Parse-only. | |
| - name: Check MCP registry metadata is in sync | |
| uses: ./.github/actions/run-gate | |
| with: { gate: mcp-metadata } | |
| freerange: | |
| name: FreeRange | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| - name: Check numeric ranges | |
| uses: ./.github/actions/run-gate | |
| with: { gate: freerange } | |
| integration: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| - name: Run integration tests | |
| run: pnpm clean:daemon | |
| - name: Execute integration tests | |
| uses: ./.github/actions/run-gate | |
| with: { gate: integration-node } | |
| - name: Run seeded concurrency torture lane (fast PR sweep) | |
| # #1416's nightly torture lane lives under test/integration/nightly/, out | |
| # of the test:integration:node glob, so this is a *deliberate* fast PR | |
| # sweep (TORTURE_RUNS default 128 seeds, ~sub-second) — not an accidental | |
| # glob inclusion. The Concurrency Torture Nightly workflow sweeps a much | |
| # larger seed range on schedule. | |
| uses: ./.github/actions/run-gate | |
| with: { gate: concurrency-torture } | |
| - name: Run provider-backed integration tests | |
| uses: ./.github/actions/run-gate | |
| with: { gate: provider-integration } | |
| - name: Check Provider-backed integration architecture progress | |
| uses: ./.github/actions/run-gate | |
| with: { gate: integration-progress } | |
| # A build-cache lookup outage must degrade setup-fixture-app to an inline | |
| # build, not fail the caller. This drives that step's real shell against a | |
| # failing `gh`. | |
| - name: Setup-fixture-app cache-failure fallback | |
| uses: ./.github/actions/run-gate | |
| with: { gate: fixture-fallback } | |
| # The trusted-artifact contract the device lanes rely on to decide a cached fixture | |
| # app is the one this commit expects. A real test file that no workflow ran. | |
| - name: Check the trusted fixture-artifact contract | |
| uses: ./.github/actions/run-gate | |
| with: { gate: fixture-cache } | |
| web-smoke: | |
| name: Web Platform Smoke | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| AGENT_DEVICE_WEB_E2E: '1' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup toolchain | |
| uses: ./.github/actions/setup-node-pnpm | |
| with: | |
| node-version: '24.13' | |
| - name: Run live web smoke | |
| run: pnpm clean:daemon | |
| - name: Execute live web smoke | |
| uses: ./.github/actions/run-gate | |
| with: { gate: web-smoke } | |
| - name: Upload web smoke artifacts | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: web-smoke-artifacts | |
| if-no-files-found: ignore | |
| path: | | |
| test/artifacts/web/** |