mirror of
https://github.com/block/buzz.git
synced 2026-11-14 05:12:27 +00:00
## Summary
Two separate CI breaks are hitting every PR right now. Each one blocks a
required check, and neither fix can go green on its own, so this PR
fixes both.
**1. Rust test targets stopped compiling on main at fc8c8022.** #9227
added a required `RunCause` argument to `create_workflow_run` and a
`may_direct_agents` flag to `ActionSink::send_message`. Two tests that
merged around the same time still used the old signatures:
- `attach_rejects_run_from_another_workflow` in
`crates/buzz-db/src/store/workflow.rs` (from #8317)
- the agent-DM refusal test in `crates/buzz-relay/src/workflow_sink.rs`
(from #9371)
Each PR passed CI on its own base. Together they break Rust Lint, Unit
Tests, Windows Rust and the Desktop E2E Relay artifact build (main CI
run 43291564834). The fix passes `RunCause::Schedule` and `true`,
matching the neighbouring tests. Neither test is about run cause or
agent mentions, so neither assertion changes.
**2. Desktop E2E Integration fails on GitHub's new Ubuntu 26.04
runners.** `ubuntu-latest` is rolling to 26.04, and Playwright 1.60 (our
lockfile) fails with `Playwright does not support chromium on
ubuntu26.04-x64`. Playwright 1.61 is the first release that knows
ubuntu26.04. This PR bumps `@playwright/test` to `^1.65.0` in
`desktop/`, `web/` and `admin-web/` and regenerates `pnpm-lock.yaml`. It
also pins every `ubuntu-latest` job in `ci.yml` and `_ci-*.yml` to
`ubuntu-26.04`, so the next runner image change arrives in a reviewed
diff. macOS and Windows jobs are unchanged.
The newer Chromium bundled with Playwright 1.65 changed two smoke specs.
It reports Tailwind's `items-end` as `align-items: flow-end`
(`doctor-states` now accepts `flex-end` or `flow-end`), and it draws the
textarea resize grip differently, so the
`workflow-template-variable-autocomplete` baseline is refreshed from the
ubuntu-26.04 CI output.
It also exposed a timing race in `profile.spec.ts` "owned agent profile
stays in parity": when the Message click lands while the profile panel
is still settling, Playwright's scroll-into-view retry scrolls the
profile body, which hides the owned-agent action row (aria-hidden +
inert) for good. The test now pins the body to the top and waits for the
row before clicking (12/12 repeats plus the full profile spec pass on
Linux).
## Verification
- `cargo clippy -p buzz-db -p buzz-relay --tests -- -D warnings` is
clean on fc8c8022 with commit 1 applied.
- `pnpm install --frozen-lockfile` succeeds with the new lockfile, and
`playwright --version` reports 1.65.0.
- `tsc --noEmit` passes for `desktop`, `web` and `admin-web`.
- This PR's CI is the E2E check on 26.04.
## Notes
- Workflows that don't run on PRs (release, helm charts, sprig, image
builds, codex-security-review) still use `ubuntu-latest`. This PR can't
exercise them, so they're left for a follow-up.
- The hermit-pinned actionlint (1.7.12) doesn't know the `ubuntu-26.04`
label yet. CI only actionlints `codex-security-review.yml`, which this
PR doesn't touch.
## Simpler
No production code changes. The runner pin swaps an implicit moving
target for an explicit one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
340 lines
15 KiB
YAML
340 lines
15 KiB
YAML
name: CI / Desktop
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
rust:
|
|
required: true
|
|
type: boolean
|
|
desktop:
|
|
required: true
|
|
type: boolean
|
|
desktop_rust:
|
|
required: true
|
|
type: boolean
|
|
outputs:
|
|
desktop_result:
|
|
value: ${{ jobs.results.outputs.desktop_result }}
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
BUZZ_TEST_POSTGRES_PASSWORD: buzz_dev
|
|
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.cache/ms-playwright
|
|
|
|
jobs:
|
|
# Biome, node:test, and the Vite build do not compile Rust, but node:test
|
|
# also pins desktop constants to relay/DB crate sources (for example
|
|
# relayReconnectReplay.test.mjs), so Rust changes select this job too.
|
|
desktop-js:
|
|
name: Desktop JS
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 20
|
|
# Keep in sync with JS_SELECTED in the Desktop gate (pinned by ci-required-gates.test.mjs).
|
|
if: github.event_name == 'push' || inputs.desktop || inputs.rust
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
fetch-depth: 2
|
|
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
|
- name: Get pnpm store directory
|
|
id: pnpm-cache
|
|
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
|
- name: Restore pnpm store cache
|
|
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
restore-keys: pnpm-${{ runner.os }}-
|
|
- name: Install desktop dependencies
|
|
run: just desktop-install-ci
|
|
- name: Desktop lint and format
|
|
run: just desktop-check
|
|
- name: Desktop unit tests
|
|
run: just desktop-test
|
|
- name: Flake check selection tests
|
|
run: node --test scripts/flake-check.test.mjs
|
|
- name: Desktop build
|
|
run: just desktop-build
|
|
- name: Save pnpm store cache
|
|
if: github.event_name == 'push'
|
|
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
|
|
# Tauri Rust lanes run in parallel. The compiled-flags lane only rebuilds
|
|
# buzz_lib under other BUZZ_BUILD_* values, so it restores the core lane's
|
|
# cache read-only; the mesh-llm feature pulls its own dependency graph.
|
|
desktop-tauri:
|
|
name: Desktop Tauri (${{ matrix.lane }})
|
|
runs-on: ubuntu-26.04
|
|
# A lane needs up to ~15m. ci-apt-retry.sh bounds the apt steps at ~34m worst
|
|
# case (update ~8.5m + install ~25m); 55m fits both, so a degraded mirror
|
|
# fails with the wrapper's error instead of the job timeout.
|
|
timeout-minutes: 55
|
|
# Keep in sync with RUST_SELECTED in the Desktop gate (pinned by ci-required-gates.test.mjs).
|
|
if: github.event_name == 'push' || inputs.desktop_rust || inputs.rust
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- lane: core
|
|
cache: desktop-core
|
|
save-cache: true
|
|
run: just desktop-tauri-clippy && just desktop-tauri-check && just desktop-tauri-test
|
|
- lane: mesh
|
|
cache: desktop-mesh
|
|
save-cache: true
|
|
run: just desktop-tauri-test-mesh
|
|
- lane: compiled-flags
|
|
cache: desktop-core
|
|
save-cache: false
|
|
run: just desktop-tauri-test-compiled-flags
|
|
permissions:
|
|
contents: read
|
|
env:
|
|
# Resolved and installed by the steps below; the .deb cache key hashes
|
|
# what apt resolves for this list.
|
|
TAURI_APT_PACKAGES: >-
|
|
build-essential
|
|
curl
|
|
file
|
|
libasound2-dev
|
|
libayatana-appindicator3-dev
|
|
libgtk-3-dev
|
|
librsvg2-dev
|
|
libssl-dev
|
|
libwebkit2gtk-4.1-dev
|
|
libxdo-dev
|
|
patchelf
|
|
wget
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
fetch-depth: 2
|
|
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
|
- uses: rui314/setup-mold@7e4f20ad28a2e8ca6fd0892ccf72e2abb706b9c3 # v1
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
|
with:
|
|
workspaces: desktop/src-tauri
|
|
shared-key: ${{ matrix.cache }}
|
|
save-if: ${{ matrix.save-cache && github.event_name != 'pull_request' }}
|
|
# A degraded Ubuntu mirror serves .debs at ~50 KB/s, and the ~62 MB
|
|
# Tauri download (one 25.5 MB libwebkit2gtk .deb) then outlasts every
|
|
# ci-apt-retry.sh attempt. Cache the .debs in the Actions cache so the
|
|
# mirror only serves `apt-get update` and packages Ubuntu has updated
|
|
# since main last saved.
|
|
- name: Resolve Tauri apt archives
|
|
id: tauri-debs
|
|
run: |
|
|
set -euo pipefail
|
|
CI_APT_ATTEMPT_SECONDS=90 scripts/ci-apt-retry.sh sudo apt-get update
|
|
read -ra packages <<< "$TAURI_APT_PACKAGES"
|
|
# Name, size and SHA256 (from the signed index) of each .deb the
|
|
# install will fetch. The mirror URI is left out so the key doesn't
|
|
# depend on which mirror answered. Runs before the restore:
|
|
# --print-uris omits files that are already in
|
|
# /var/cache/apt/archives.
|
|
sudo apt-get install --print-uris -qq -y --no-install-recommends \
|
|
-o Acquire::ForceHash=SHA256 "${packages[@]}" |
|
|
awk '{print $2, $3, $4}' | sort > "$RUNNER_TEMP/tauri-debs.txt"
|
|
. /etc/os-release
|
|
prefix="tauri-debs-${ID}${VERSION_ID}-"
|
|
echo "prefix=$prefix" >> "$GITHUB_OUTPUT"
|
|
echo "key=$prefix$(sha256sum "$RUNNER_TEMP/tauri-debs.txt" | cut -c1-16)" >> "$GITHUB_OUTPUT"
|
|
- name: Restore Tauri apt archives
|
|
id: tauri-debs-cache
|
|
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ~/.cache/tauri-debs
|
|
key: ${{ steps.tauri-debs.outputs.key }}
|
|
restore-keys: ${{ steps.tauri-debs.outputs.prefix }}
|
|
- name: Install Tauri dependencies (Linux)
|
|
id: tauri-debs-install
|
|
env:
|
|
DEBIAN_FRONTEND: noninteractive
|
|
run: |
|
|
set -euo pipefail
|
|
debs=~/.cache/tauri-debs
|
|
mkdir -p "$debs"
|
|
# apt reuses an archived .deb of the expected size without checking
|
|
# its hash, so seed only restored files whose SHA256 matches the
|
|
# signed index. apt downloads and verifies the rest. sha256sum exits
|
|
# non-zero when a file fails or none is restored; neither is fatal.
|
|
awk '{sub(/^SHA256:/, "", $3); print $3 " " $1}' "$RUNNER_TEMP/tauri-debs.txt" > "$RUNNER_TEMP/tauri-debs.sha256"
|
|
(cd "$debs" && { sha256sum --check --ignore-missing "$RUNNER_TEMP/tauri-debs.sha256" || true; }) |
|
|
sed -n 's/: OK$//p' | (cd "$debs" && xargs -r sudo cp -t /var/cache/apt/archives/)
|
|
read -ra packages <<< "$TAURI_APT_PACKAGES"
|
|
scripts/ci-apt-retry.sh sudo apt-get install -y --no-install-recommends "${packages[@]}"
|
|
# Keep exactly the resolved files, so a restore-keys fallback can't
|
|
# pile up superseded versions. The packages are installed by now, so
|
|
# a failure here only warns and skips the save.
|
|
if rm -rf "$debs" && mkdir -p "$debs" &&
|
|
awk '{print "/var/cache/apt/archives/" $1}' "$RUNNER_TEMP/tauri-debs.txt" | xargs -r cp -t "$debs"; then
|
|
echo "rebuilt=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "::warning::Could not rebuild the Tauri apt archive cache; not saving it."
|
|
fi
|
|
# Like the other caches here, only pushes save; PR runs restore. All
|
|
# three lanes resolve the same key, so one lane saves it.
|
|
- name: Save Tauri apt archives
|
|
if: matrix.lane == 'core' && github.event_name == 'push' && steps.tauri-debs-cache.outputs.cache-hit != 'true' && steps.tauri-debs-install.outputs.rebuilt == 'true'
|
|
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ~/.cache/tauri-debs
|
|
key: ${{ steps.tauri-debs.outputs.key }}
|
|
- name: Desktop Tauri ${{ matrix.lane }}
|
|
run: ${{ matrix.run }}
|
|
env:
|
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
|
|
|
desktop-smoke-e2e:
|
|
name: Desktop Smoke E2E (${{ matrix.shard }})
|
|
runs-on: ubuntu-26.04
|
|
# A smoke shard needs ~12m, and Chromium dependency setup is bounded by
|
|
# ci-apt-retry.sh (~25m worst case). Leave time for the full suite
|
|
# and its reports to finish.
|
|
timeout-minutes: 45
|
|
# Keep in sync with FRONTEND_SELECTED in the Desktop gate (pinned by ci-required-gates.test.mjs).
|
|
if: github.event_name == 'push' || inputs.desktop
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# Playwright shards smoke specs by test count, not duration; ten
|
|
# shards keep the slowest one near 11 minutes of test time.
|
|
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
|
- name: Get pnpm store directory
|
|
id: pnpm-cache
|
|
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
|
- name: Restore pnpm store cache
|
|
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
restore-keys: pnpm-${{ runner.os }}-
|
|
- name: Install desktop dependencies
|
|
run: just desktop-install-ci
|
|
- name: Get Playwright version
|
|
id: pw-version
|
|
run: echo "version=$(cd desktop && node -e "console.log(require('@playwright/test/package.json').version)")" >> "$GITHUB_OUTPUT"
|
|
- name: Restore Playwright browser cache
|
|
id: playwright-cache
|
|
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
|
|
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
|
|
- name: Install Playwright Chromium
|
|
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
|
run: cd desktop && pnpm exec playwright install chromium
|
|
- name: Install Playwright system dependencies
|
|
run: cd desktop && ../scripts/ci-apt-retry.sh pnpm exec playwright install-deps chromium
|
|
- name: Save Playwright browser cache
|
|
if: steps.playwright-cache.outputs.cache-hit != 'true' && github.event_name == 'push' && matrix.shard == 1
|
|
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
|
|
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}
|
|
- name: Desktop E2E build
|
|
run: pnpm -C desktop build:e2e
|
|
- name: Desktop smoke e2e
|
|
run: cd desktop && pnpm exec playwright test --project=smoke --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
|
- name: Summarize flaky tests
|
|
if: ${{ !cancelled() }}
|
|
run: node scripts/summarize-flaky-tests.mjs playwright-report.json "Desktop Smoke E2E (${{ matrix.shard }})"
|
|
working-directory: desktop
|
|
- name: Upload desktop smoke e2e artifacts
|
|
if: ${{ !cancelled() }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: desktop-smoke-e2e-artifacts-${{ matrix.shard }}
|
|
path: |
|
|
desktop/playwright-report
|
|
desktop/playwright-report.json
|
|
desktop/test-results
|
|
if-no-files-found: ignore
|
|
retention-days: 7
|
|
|
|
desktop:
|
|
name: Desktop
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 5
|
|
needs: [desktop-js, desktop-tauri, desktop-smoke-e2e, desktop-windows-build]
|
|
if: always() && (github.event_name == 'push' || inputs.desktop || inputs.desktop_rust || inputs.rust)
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Check desktop jobs
|
|
env:
|
|
JS_SELECTED: ${{ github.event_name == 'push' || inputs.desktop || inputs.rust }}
|
|
RUST_SELECTED: ${{ github.event_name == 'push' || inputs.desktop_rust || inputs.rust }}
|
|
FRONTEND_SELECTED: ${{ github.event_name == 'push' || inputs.desktop }}
|
|
JS: ${{ needs.desktop-js.result }}
|
|
TAURI: ${{ needs.desktop-tauri.result }}
|
|
SMOKE: ${{ needs.desktop-smoke-e2e.result }}
|
|
WINDOWS: ${{ needs.desktop-windows-build.result }}
|
|
run: |
|
|
# A selected job must succeed; an unselected job must be skipped.
|
|
status=0
|
|
check() {
|
|
local name="$1" selected="$2" result="$3" want=skipped
|
|
[ "$selected" = true ] && want=success
|
|
if [ "$result" != "$want" ]; then
|
|
echo "$name finished with: $result (expected $want)"
|
|
status=1
|
|
fi
|
|
}
|
|
check "Desktop JS" "$JS_SELECTED" "$JS"
|
|
check "Desktop Tauri lanes" "$RUST_SELECTED" "$TAURI"
|
|
check "Desktop Smoke E2E shards" "$FRONTEND_SELECTED" "$SMOKE"
|
|
check "Desktop Windows Build" "$FRONTEND_SELECTED" "$WINDOWS"
|
|
[ "$status" = 0 ] && echo "Desktop jobs passed"
|
|
exit "$status"
|
|
|
|
desktop-windows-build:
|
|
name: Desktop Windows Build
|
|
runs-on: windows-latest
|
|
timeout-minutes: 20
|
|
# Keep in sync with FRONTEND_SELECTED in the Desktop gate (pinned by ci-required-gates.test.mjs).
|
|
if: github.event_name == 'push' || inputs.desktop
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24.14.1
|
|
package-manager-cache: false
|
|
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
|
with:
|
|
version: 11.4.0
|
|
- name: Install desktop dependencies
|
|
shell: bash
|
|
run: pnpm install --frozen-lockfile
|
|
- name: Build both protected-feature selections
|
|
shell: pwsh
|
|
run: |
|
|
Remove-Item Env:VITE_BUZZ_BESTIE -ErrorAction SilentlyContinue
|
|
pnpm -C desktop build
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
$env:VITE_BUZZ_BESTIE = "1"
|
|
pnpm -C desktop build
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
results:
|
|
name: Results
|
|
if: ${{ always() }}
|
|
needs: [desktop]
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 5
|
|
permissions: {}
|
|
outputs:
|
|
desktop_result: ${{ needs.desktop.result }}
|
|
steps:
|
|
- run: echo "Captured Desktop job results"
|