Skip to content

chore(deps): bump rio-vt from 0.5.25 to 0.5.26 #8027

chore(deps): bump rio-vt from 0.5.25 to 0.5.26

chore(deps): bump rio-vt from 0.5.25 to 0.5.26 #8027

Workflow file for this run

name: CI
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
schedule:
- cron: '31 6 * * 1'
workflow_dispatch:
inputs:
expected_sha:
description: Exact 40-character commit selected by --ref (manual runs always force full CI)
required: true
type: string
permissions:
contents: read
concurrency:
# PRs still share one group so a new push cancels the superseded head.
# Push/schedule/dispatch on main must be keyed by SHA: with cancel-in-progress
# false, GitHub still cancels a *pending* run in the same group when a new
# one queues. That is how 31 of the last 40 main CI runs vanished without a
# verdict (test bankruptcy, 2026-08-19). Each SHA gets its own group so
# every commit on main actually finishes.
group: ${{ github.event_name == 'pull_request' && format('ci-pr-{0}', github.event.pull_request.number) || format('ci-{0}-{1}', github.workflow, github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
# Test threads share a process and tokio/async frames run deep; the default
# 2 MiB stack overflowed sporadically in runtime_api::tests::start_turn_*
# under load and aborted the whole lib suite (signal 6). 8 MiB is the
# measured-safe floor; nextest's per-process runs are unaffected either way.
RUST_MIN_STACK: 8388608
jobs:
changes:
name: Change detection
timeout-minutes: 10
runs-on: ubuntu-latest
outputs:
heavy: ${{ steps.detect.outputs.heavy }}
workflow: ${{ steps.detect.outputs.workflow }}
mobile: ${{ steps.detect.outputs.mobile }}
actions: ${{ steps.detect.outputs.actions }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Detect executable changes
id: detect
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
BEFORE_SHA: ${{ github.event.before }}
EXPECTED_SHA: ${{ inputs.expected_sha }}
run: |
set -euo pipefail
if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then
if [[ "${#EXPECTED_SHA}" -ne 40 || "${EXPECTED_SHA}" =~ [^0-9a-fA-F] ]]; then
echo "::error::expected_sha must be a full 40-character commit SHA." >&2
exit 1
fi
actual="$(git rev-parse HEAD)"
expected_normalized="$(printf '%s' "${EXPECTED_SHA}" | tr '[:upper:]' '[:lower:]')"
if [[ "${actual}" != "${expected_normalized}" ]]; then
echo "::error::Dispatch resolved to ${actual}, not requested ${EXPECTED_SHA}." >&2
exit 1
fi
echo "Manual exact-head dispatch: forcing heavy, workflow, mobile, and action gates."
echo "heavy=true" >> "${GITHUB_OUTPUT}"
echo "workflow=true" >> "${GITHUB_OUTPUT}"
echo "mobile=true" >> "${GITHUB_OUTPUT}"
echo "actions=true" >> "${GITHUB_OUTPUT}"
exit 0
fi
if [[ "${EVENT_NAME}" == "schedule" ]]; then
echo "heavy=true" >> "${GITHUB_OUTPUT}"
echo "workflow=true" >> "${GITHUB_OUTPUT}"
echo "mobile=true" >> "${GITHUB_OUTPUT}"
echo "actions=true" >> "${GITHUB_OUTPUT}"
exit 0
fi
base=""
if [[ "${EVENT_NAME}" == "pull_request" && -n "${BASE_REF}" ]]; then
git fetch --no-tags origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}" --depth=1
base="origin/${BASE_REF}"
elif [[ -n "${BEFORE_SHA}" && "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]]; then
base="${BEFORE_SHA}"
fi
if [[ -z "${base}" ]]; then
echo "heavy=true" >> "${GITHUB_OUTPUT}"
echo "workflow=true" >> "${GITHUB_OUTPUT}"
echo "mobile=true" >> "${GITHUB_OUTPUT}"
echo "actions=true" >> "${GITHUB_OUTPUT}"
exit 0
fi
mapfile -t changed < <(git diff --name-only "${base}" "${GITHUB_SHA}" | sort)
heavy=false
workflow=false
mobile=false
actions=false
for path in "${changed[@]}"; do
# Heavy classification. ORDER MATTERS: must-stay-heavy inputs are
# matched BEFORE any light entry so a script that only a
# heavy-gated job exercises can never be misclassified as light.
# Anything unrecognized falls through to the default-heavy `*)`
# arm (fail-safe default-heavy). Light-classified scripts below
# are exercised by ALWAYS-on jobs/steps that run regardless of
# `heavy` (check-versions.sh / check-ohos-deps.sh via Version
# drift, check-coauthor-trailers.py via Lint, dev-cache/dev-test
# self-checks via Version drift), so no coverage is lost.
case "${path}" in
scripts/release/npm-wrapper-smoke.js|scripts/mobile-smoke.sh|scripts/check-provider-registry.py)
heavy=true
;;
docs/*|*.md|packaging/aur/*|.github/PULL_REQUEST_TEMPLATE.md|.github/ISSUE_TEMPLATE/*|.github/scripts/agent-task-metadata.test.sh|.github/workflows/agent-task-labels.yml|.github/workflows/auto-tag.yml|.github/workflows/stale.yml|.github/workflows/triage.yml|scripts/release/check-versions.sh|scripts/release/check-ohos-deps.sh|scripts/release/install-dogfood.sh|scripts/release/install-dogfood.test.sh|scripts/release/prepare-release.sh|scripts/release/prepare-release.test.sh|scripts/check-coauthor-trailers.py|scripts/dev-cache.sh|scripts/dev-cache.test.sh|scripts/dev-cargo.sh|scripts/dev-test.sh)
;;
*)
heavy=true
;;
esac
case "${path}" in
crates/workflow/*|.github/workflows/ci.yml)
workflow=true
;;
esac
# Mobile runtime surface: the `codewhale serve --mobile`
# HTTP/SSE stack that scripts/mobile-smoke.sh exercises. Pull
# requests run the smoke only when one of these changes; every
# push to main still runs it unconditionally as the pre-release
# safety net for anything this filter misses.
case "${path}" in
crates/app-server/*|crates/tui/src/runtime_api*|crates/tui/src/runtime_mobile.html|crates/tui/src/runtime_threads*|crates/tui/src/main.rs|scripts/mobile-smoke.sh|.github/workflows/ci.yml|Cargo.lock|Cargo.toml)
mobile=true
;;
esac
case "${path}" in
.github/workflows/*|.github/actionlint.yml)
actions=true
;;
esac
done
echo "heavy=${heavy}" >> "${GITHUB_OUTPUT}"
echo "workflow=${workflow}" >> "${GITHUB_OUTPUT}"
echo "mobile=${mobile}" >> "${GITHUB_OUTPUT}"
echo "actions=${actions}" >> "${GITHUB_OUTPUT}"
echo "Heavy Rust CI required: ${heavy}"
echo "Workflow RLM cache CI required: ${workflow}"
echo "Mobile runtime smoke required (PRs): ${mobile}"
echo "Workflow lint required: ${actions}"
printf 'Changed files:\n'
printf ' %s\n' "${changed[@]}"
versions:
name: Version drift
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-node@v7
with:
node-version: 20
- name: Check version drift
run: ./scripts/release/check-versions.sh
- name: Check OHOS dependency graph
run: ./scripts/release/check-ohos-deps.sh
- name: Check release helper contracts
run: |
bash .github/scripts/agent-task-metadata.test.sh
bash scripts/release/check-feature-release-notes.test.sh
bash scripts/release/generate-release-body.test.sh
bash scripts/release/install-dogfood.test.sh
bash scripts/release/prepare-release.test.sh
bash scripts/release/require-release-tag-checkout.test.sh
bash scripts/release/validate-crate-publish-order.test.sh
bash scripts/release/verify-remote-tag.test.sh
bash packaging/aur/render.test.sh
sh scripts/dev-cache.test.sh
bash .github/scripts/update-homebrew-tap.test.sh
node .github/scripts/release-workflows.test.js
node --test scripts/release/assemble-release-assets.test.js
node --test scripts/release/ensure-release-assets-absent.test.js
- name: Run runtime web client tests
# crates/tui/tests/runtime_web_client.test.mjs exercises the embedded
# web client's event/snapshot state machine; it ran nowhere before.
run: node --test crates/tui/tests/runtime_web_client.test.mjs
integrations:
name: Integrations
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Run chat-bridge suites
# All four bridges + bridge-core ship dependency-free node --test
# suites that no workflow ran. weixin has no lockfile by design
# (zero deps); npm test works without npm ci everywhere here.
run: |
set -euo pipefail
for bridge in bridge-core feishu-bridge telegram-bridge wecom-bridge weixin-bridge; do
echo "== ${bridge}"
(cd "integrations/${bridge}" && npm test)
done
safety-gate:
name: Safety gate
needs: changes
if: needs.changes.outputs.heavy == 'true'
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: mozilla-actions/sccache-action@v0.0.11
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- name: Install Linux system dependencies
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Hermetic safety and authorization tests
env:
HOME: ${{ runner.temp }}/cw-hermetic-home
USERPROFILE: ${{ runner.temp }}/cw-hermetic-home
CODEWHALE_HOME: ${{ runner.temp }}/cw-hermetic-home/.codewhale
RUST_MIN_STACK: "8388608"
run: |
mkdir -p "${HOME}" "${CODEWHALE_HOME}"
unset CODEWHALE_CONFIG_PATH DEEPSEEK_CONFIG_PATH DEEPSEEK_HOME || true
cargo test -p codewhale-tui --lib --locked -- command_safety auto_review authority sandbox
cargo test -p codewhale-execpolicy --locked
lint:
name: Lint
needs: changes
timeout-minutes: 45
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@master
if: needs.changes.outputs.heavy == 'true'
with:
toolchain: stable
components: rustfmt, clippy
- uses: mozilla-actions/sccache-action@v0.0.11
id: sccache
# Cache bootstrap failures (e.g. GitHub 504s fetching the sccache
# binary) degrade to an uncached build instead of failing product CI.
continue-on-error: true
if: needs.changes.outputs.heavy == 'true'
- name: Enable sccache
if: needs.changes.outputs.heavy == 'true' && steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- name: Install Linux system dependencies
if: needs.changes.outputs.heavy == 'true'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
if: needs.changes.outputs.heavy == 'true'
with:
cache-bin: false
# PRs restore the cache seeded by main but skip the expensive
# post-job save; sccache covers PR-specific compilation deltas.
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Check formatting
if: needs.changes.outputs.heavy == 'true'
run: cargo fmt --all -- --check
- name: Run clippy
# --all-targets, because without it CI never lints test code at all.
# That gap is not theoretical: the v0.9.10 release gate opened with
# four clippy failures sitting on a green main, and every one of them
# was in a test target. crates/tui/AGENTS.md already documents the
# all-targets command as the release gate; this makes CI run the gate
# it points contributors at instead of a weaker subset.
#
# collapsible_if and assertions_on_constants are no longer allowed for
# the same reason — they were three of those four, so the allowances
# were hiding exactly the class of problem that reached the gate. The
# three that remain are deliberate project style, not oversights.
if: needs.changes.outputs.heavy == 'true'
run: |
cargo clippy --workspace --all-targets --all-features --locked -- \
-D warnings \
-A clippy::uninlined_format_args \
-A clippy::too_many_arguments \
-A clippy::unnecessary_map_or
- name: sccache stats
if: needs.changes.outputs.heavy == 'true' && steps.sccache.outcome == 'success'
continue-on-error: true
shell: bash
run: sccache --show-stats
- name: Check provider registry drift
if: needs.changes.outputs.heavy == 'true'
run: python3 scripts/check-provider-registry.py
- name: Check command-contract prototype boundary
if: needs.changes.outputs.heavy == 'true'
run: |
python3 scripts/test_check_command_crate_boundaries.py
python3 scripts/check-command-crate-boundaries.py
- name: Check command migration manifest
if: needs.changes.outputs.heavy == 'true'
env:
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
python3 scripts/test_check_command_migration_manifest.py
baseline="${PR_BASE_SHA:-${PUSH_BEFORE_SHA:-}}"
if [[ -n "${baseline}" && ! "${baseline}" =~ ^0+$ ]]; then
git fetch --no-tags origin "${baseline}"
python3 scripts/check-command-migration-manifest.py --baseline-ref "${baseline}"
else
python3 scripts/check-command-migration-manifest.py
fi
# Clippy above runs without `--all-targets`, so it cannot see dead code
# that only tests keep alive. This ratchet covers that blind spot by
# refusing to let the `#[allow(dead_code)]` total rise (#4785).
- name: Check dead-code budget
if: needs.changes.outputs.heavy == 'true'
run: python3 scripts/check-dead-code-budget.py
- name: Test runtime-contract measurement harness
if: needs.changes.outputs.heavy == 'true'
run: |
python3 scripts/test_measure_runtime_contract.py
python3 scripts/test_check_runtime_contract_budget.py
# The offline runtime-contract measurement needs the full locked graph,
# dev-dependencies included (e.g. wiremock -> assert-json-diff), but
# clippy above builds no test targets and the rust-cache registry key
# derives from Cargo.lock, so any lock-changing PR (every dependabot
# bump) restores an empty cache and the hermetic `cargo test --offline`
# dies with "failed to download ... --offline was specified" before a
# single budget is measured. Fetch the locked graph once here so the
# measurement below is deterministic on every branch.
- name: Fetch locked dependency graph for offline measurement
if: needs.changes.outputs.heavy == 'true'
run: cargo fetch --locked
# Provider-free local measurement. The checker forces Cargo offline and
# the measurement script runs only locked, ignored Rust metric tests.
- name: Check runtime-contract budget
if: needs.changes.outputs.heavy == 'true'
run: python3 scripts/check-runtime-contract-budget.py
# Provider-free paused-consumer measurement of the production
# persistence request channel. RSS is sampled only on macOS; every host
# enforces the accepted/retained request and payload contract.
- name: Test persistence-backlog measurement and checker harnesses
if: needs.changes.outputs.heavy == 'true'
run: |
python3 scripts/test_measure_persistence_backlog.py
python3 scripts/test_check_persistence_backlog_budget.py
- name: Check persistence-backlog budget
if: needs.changes.outputs.heavy == 'true'
run: python3 scripts/check-persistence-backlog-budget.py
- name: Check README translations stay in sync
if: github.event_name != 'schedule'
run: python3 scripts/check-readme-translations.py
- name: Check README locale link symmetry
if: github.event_name != 'schedule'
run: bash scripts/check-readme-locales.sh
- name: Check TUI locale pack parity
if: github.event_name != 'schedule'
run: python3 scripts/check-tui-locale-parity.py
- name: Check website locale dictionary parity
if: github.event_name != 'schedule'
run: node web/scripts/check-locales.mjs
- name: Check harvested contributor credit
if: github.event_name != 'schedule'
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
PR_MERGE_SHA: ${{ github.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
python3 scripts/test_check_coauthor_trailers.py
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
# Pull-request base.sha can lag the merge ref's actual base parent.
# Comparing it to the synthetic merge checkout therefore
# rechecks unrelated commits that landed on the base after the PR
# opened. Use the merge ref's immutable parents: the base snapshot
# used to synthesize it and the exact PR head, while excluding the
# synthetic merge itself.
if [[ "$(git rev-parse HEAD)" != "${PR_MERGE_SHA}" ]]; then
echo "::error::credit check is not running at the event merge SHA" >&2
exit 1
fi
read -r BASE_PARENT HEAD_PARENT EXTRA_PARENT < <(
git show -s --format='%P' "${PR_MERGE_SHA}"
)
if [[ -z "${BASE_PARENT}" || -z "${HEAD_PARENT}" || -n "${EXTRA_PARENT}" ]]; then
echo "::error::pull-request merge ref must have exactly two parents" >&2
exit 1
fi
if [[ "${HEAD_PARENT}" != "${PR_HEAD_SHA}" ]]; then
echo "::error::merge ref head parent does not match the event PR head" >&2
exit 1
fi
RANGE="${BASE_PARENT}..${HEAD_PARENT}"
if [[ -z "$(git rev-list -1 "${RANGE}")" ]]; then
echo "::error::pull-request credit range is empty" >&2
exit 1
fi
elif [[ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]]; then
RANGE="${{ github.event.before }}..${{ github.sha }}"
else
RANGE="HEAD~1..HEAD"
fi
python3 scripts/check-coauthor-trailers.py \
--author-map .github/AUTHOR_MAP \
--range "$RANGE" \
--check-authors
- name: Skip Rust lint for light change
if: needs.changes.outputs.heavy != 'true'
run: echo "No executable Rust changes detected; preserving required Lint context."
- name: Linux clippy location
if: needs.changes.outputs.heavy == 'true'
run: echo "Linux clippy/test gates run on CNB for mirrored fix/*, rebrand/*, work/v*, and main branches."
workflow-rlm-cache:
name: Workflow RLM cache
needs: changes
if: needs.changes.outputs.workflow == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.11
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Run workflow crate tests
run: cargo test -p codewhale-workflow --locked
test:
name: Test
needs: changes
# Required contexts "Test (ubuntu-latest)" / "Test (macos-latest)" /
# "Test (windows-latest)" derive from job name + matrix.os and are
# independent of runs-on. For light changes the macOS/Windows legs only
# echo a skip line, so run them on ubuntu instead of queueing for scarce
# macOS/Windows runners. Heavy pull requests run the Linux lane directly;
# non-PR release/main pushes use CNB for Linux.
# The ternary is safe: matrix.os is always a non-empty literal, so
# runs-on can never evaluate to empty.
timeout-minutes: 90
runs-on: ${{ needs.changes.outputs.heavy == 'true' && matrix.os || 'ubuntu-latest' }}
strategy:
# A failure on one desktop platform must not erase evidence from the
# other one. We need both conclusions to diagnose and release safely.
fail-fast: false
matrix:
# Linux workspace tests run directly for pull requests. CNB remains
# the Linux lane for non-PR release/main pushes.
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Skip tests for light change
if: needs.changes.outputs.heavy != 'true'
run: echo "No executable Rust changes detected; preserving required Test context."
- uses: actions/checkout@v7
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- name: Test Windows installer PATH helper
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'windows-latest'
shell: pwsh
run: ./scripts/installer/update-user-path.tests.ps1
- name: Install NSIS for Windows installer regression
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'windows-latest'
shell: pwsh
# Bounded retry, not a weaker check (#5403). Every observed failure here
# was Chocolatey's feed, not the code: a 504 from the V2 API, and
# "package was not found with the source(s) listed". A single attempt
# made `Test (windows-latest)` — a required check on every PR — report
# on community.chocolatey.org's availability instead of on the tree.
# NSIS must still install for the regression below to run; this only
# survives a transient outage.
run: |
$ErrorActionPreference = 'Continue'
$delays = @(0, 20, 45)
for ($attempt = 0; $attempt -lt $delays.Count; $attempt++) {
if ($delays[$attempt] -gt 0) {
Write-Host "NSIS install attempt $($attempt + 1) after $($delays[$attempt])s backoff"
Start-Sleep -Seconds $delays[$attempt]
}
choco install nsis -y --no-progress
if ($LASTEXITCODE -eq 0) {
Write-Host "NSIS installed on attempt $($attempt + 1)"
exit 0
}
Write-Host "::warning::choco install nsis failed (exit $LASTEXITCODE)"
}
Write-Host "::error::NSIS could not be provisioned from Chocolatey after $($delays.Count) attempts"
exit 1
- name: Test Windows installer PATH regression
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'windows-latest'
shell: pwsh
run: ./scripts/installer/installer-path-regression.tests.ps1 -AllowUserPathMutation
- uses: dtolnay/rust-toolchain@stable
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- uses: mozilla-actions/sccache-action@v0.0.11
id: sccache
continue-on-error: true
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- name: Enable sccache
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request') && steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- name: Install Linux system dependencies
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@nextest
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- name: Run tests
# Same test binaries as `cargo test`, run by cargo-nextest: one
# process per test, all runner cores busy, slow tests named instead
# of stalling the binary. `.config/nextest.toml` serializes the PTY
# binary and bounds the integration binary that spawns the real
# executable; retries are off, so a flake is a red run, not a hidden
# one. nextest does not run doctests — the next step keeps them.
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
run: cargo nextest run --workspace --all-features --locked --profile ci
env:
# Give test threads the stack the product gives itself. main.rs runs
# the owner thread and every tokio worker at
# CODEWHALE_MAIN_STACK_BYTES (16 MiB) because the engine and
# runtime-thread futures are genuinely deep. `#[tokio::test]` builds
# its own runtime and never sees that, so tests ran the same code on
# ~2 MiB (~1 MiB on Windows) — a configuration that never ships.
# That gap is what aborted the whole Windows test binary with
# STATUS_STACK_OVERFLOW in start_turn_accepts_dynamic_tools_and_
# environment_id, masking every other Windows result (78afd8d3d4
# Box::pin'd that one frame; the mismatch itself remained). std reads
# this for any thread spawned without an explicit size, which covers
# both libtest's per-test threads and tokio's workers.
RUST_MIN_STACK: '16777216'
- name: Run doctests
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
run: cargo test --workspace --all-features --locked --doc
env:
RUST_MIN_STACK: '16777216'
# The Ubuntu lint lane validates non-RSS backlog fields. Run the same
# source-bound measurement on macOS so loss or growth of RSS evidence
# fails closed instead of becoming an unsupported-field skip.
- name: Check persistence-backlog RSS budget
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'macos-latest'
run: python3 scripts/check-persistence-backlog-budget.py
- name: Lockfile drift guard
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
run: git diff --exit-code -- Cargo.lock
- name: Run Offline Eval Harness
# The eval harness is OS-independent prompt/composition checking;
# running it once (on the faster macOS leg, warm from the test build)
# instead of once per desktop OS keeps the coverage while taking
# ~2min off the Windows critical path.
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'macos-latest'
run: cargo run -p codewhale-tui --all-features -- eval
- name: sccache stats
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request') && steps.sccache.outcome == 'success'
continue-on-error: true
shell: bash
run: sccache --show-stats
- name: Linux test location (CNB)
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request'
run: echo "Linux workspace tests run on CNB for non-PR release/main pushes; pull requests run directly on Ubuntu."
npm-wrapper-smoke:
name: npm wrapper smoke
needs: changes
if: github.event_name != 'schedule'
# Same ternary rationale as the Test job: light legs only echo, so keep
# them off macOS/Windows runners. On pull_request the matrix is
# ubuntu-only, so the required "npm wrapper smoke (ubuntu-latest)"
# context is unaffected.
timeout-minutes: 30
runs-on: ${{ needs.changes.outputs.heavy == 'true' && matrix.os || 'ubuntu-latest' }}
strategy:
matrix:
os: ${{ fromJSON(github.event_name == 'pull_request' && '["ubuntu-latest"]' || '["ubuntu-latest","macos-latest","windows-latest"]') }}
steps:
- name: Skip npm wrapper smoke for light change
if: needs.changes.outputs.heavy != 'true'
run: echo "No executable Rust changes detected; preserving required npm wrapper smoke context."
- uses: actions/checkout@v7
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
- uses: dtolnay/rust-toolchain@stable
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
- uses: mozilla-actions/sccache-action@v0.0.11
id: sccache
continue-on-error: true
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
- name: Enable sccache
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- uses: actions/setup-node@v7
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
with:
node-version: 20
- name: Install Linux system dependencies
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name == 'workflow_dispatch'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Build wrapper binaries
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
# The smoke validates wrapper install/delegation plumbing, not
# codegen quality, so skip fat LTO + codegen-units=1 for a much
# cheaper release build. Shipped binaries keep the real profile via
# the Release workflow.
env:
CARGO_PROFILE_RELEASE_LTO: 'off'
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '16'
run: cargo build --release --locked -p codewhale-cli -p codewhale-tui
- name: Smoke wrapper install and delegated entrypoints
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
run: node scripts/release/npm-wrapper-smoke.js
- name: sccache stats
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success'
continue-on-error: true
shell: bash
run: sccache --show-stats
- name: Linux smoke location
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name != 'workflow_dispatch'
run: echo "Linux npm wrapper smoke runs on CNB for mirrored first-party branches."
mobile-smoke:
name: Mobile runtime smoke
needs: changes
# Not a required PR context. Pull requests run it only when the mobile
# runtime surface changed (see the `mobile` filter above); every push to
# main runs it unconditionally as the pre-release safety net.
if: >-
github.event_name != 'schedule' &&
needs.changes.outputs.heavy == 'true' &&
(github.event_name != 'pull_request' || needs.changes.outputs.mobile == 'true')
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.11
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- name: Install Linux system dependencies
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Run mobile smoke tests
# The smoke exercises HTTP/SSE runtime behaviour, not codegen
# quality; skipping fat LTO + codegen-units=1 cuts the in-script
# release build from ~12min to a fraction of that.
env:
CARGO_PROFILE_RELEASE_LTO: 'off'
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '16'
run: ./scripts/mobile-smoke.sh
- name: sccache stats
if: steps.sccache.outcome == 'success'
continue-on-error: true
shell: bash
run: sccache --show-stats
actionlint:
name: Workflow lint
needs: changes
if: needs.changes.outputs.actions == 'true'
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Run actionlint
uses: docker://rhysd/actionlint:1.7.12
with:
# SC2129 (grouped redirects) is style-only and endemic to the
# existing GITHUB_ENV/GITHUB_OUTPUT append pattern; SC2221/SC2222
# flag the long-standing `*.md` glob shadowing the PR-template
# entry in change detection, which is intentional.
args: -color -ignore SC2129 -ignore SC2221 -ignore SC2222
# Check documentation builds without warnings
docs:
name: Documentation
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Install Linux system dependencies
if: runner.os == 'Linux'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Build docs
run: cargo doc --workspace --no-deps
env:
RUSTDOCFLAGS: -Dwarnings