Skip to content

Fix AirPods dictation: gate recording on mic liveness, and recover the tail #588

Fix AirPods dictation: gate recording on mic liveness, and recover the tail

Fix AirPods dictation: gate recording on mic liveness, and recover the tail #588

Workflow file for this run

name: check
on:
pull_request:
branches: [main]
push:
branches: [main]
# Required for the merge queue: GitHub builds each queued PR as a temporary
# gh-readonly-queue/ candidate and fires merge_group; the required `check`
# status must run there or queued PRs never merge.
merge_group:
concurrency:
group: check-${{ github.ref }}
cancel-in-progress: true
# Minimal token scope: this workflow only checks out and builds/tests the repo.
permissions:
contents: read
jobs:
# Cheap gate: decide whether anything outside docs/ changed. docs/ is prose
# (excluded from the lint checks) and can't affect the build or tests, so a
# docs-only change skips the expensive macOS job below.
#
# Why a skipped job rather than `paths-ignore`: `check` is a required status
# check on main. `paths-ignore` filters the whole workflow, so the required
# context is never reported and the PR stays stuck "Expected". A job skipped
# via `if:` still reports — branch protection treats a skipped required check
# as passing — so docs-only PRs stay mergeable.
changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout blurt
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: blurt
fetch-depth: 0
persist-credentials: false
- name: Detect non-docs changes
id: filter
working-directory: blurt
env:
EVENT: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.sha }}
BEFORE: ${{ github.event.before }}
run: |
if [ "$EVENT" = "pull_request" ]; then
range="$BASE_SHA...$HEAD_SHA"
elif [ -n "${BEFORE:-}" ] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
range="$BEFORE..$HEAD_SHA"
else
range="" # first push / unknown base: run the full check to be safe
fi
if [ -z "$range" ]; then
echo "code=true" >>"$GITHUB_OUTPUT"; exit 0
fi
# `|| true` is the safety property, not tidiness: this step runs under
# `bash -e`, so a failing git diff (an unreachable base sha after a
# force-push, a fork edge case) fails the step, fails this job, and makes
# the `check` job SKIP — which GitHub reports to branch protection as a
# passing required check. The entire macOS gate would be bypassed on a
# green-looking PR. An empty `changed` falls through to code=true below,
# so failing open runs the full check instead.
changed="$(git diff --name-only "$range" || true)"
echo "Changed files in $range:"; echo "$changed"
# Capture non-docs lines and test emptiness (robust across grep impls).
nondocs="$(printf '%s\n' "$changed" | grep -vE '^docs/' || true)"
if [ -z "$changed" ] || [ -n "$nondocs" ]; then
echo "code=true" >>"$GITHUB_OUTPUT" # something outside docs/ changed
else
echo "code=false" >>"$GITHUB_OUTPUT" # docs-only — skip the heavy job
fi
check:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: macos-26
timeout-minutes: 30
steps:
- name: Checkout blurt
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: blurt
persist-credentials: false
- name: Install build tools
working-directory: blurt
run: brew bundle --file=Brewfile
- name: Show toolchain
run: |
xcodebuild -version
swift --version
- name: Run check.sh
working-directory: blurt
# CI=true is what makes check.sh run the whole-app integration steps (the
# XCUITest suite and the leak scan). They take over the keyboard and
# screen of whatever machine they run on, so a local check.sh skips them
# and this job is the only place they run — don't unset it. Actions
# exports CI=true for every step anyway; it's set here so the dependency
# is visible at the call site rather than only in check.sh.
env:
CI: "true"
run: scripts/check.sh
# Fast-fail typecheck. `check` above is the authority on green and this job can
# only go red where `check` would too — so it is deliberately NOT required, and
# `gate` ignores it.
#
# It exists for latency. The web sandbox has no Swift toolchain (macOS-only
# project; the Linux toolchain isn't reachable under the default network
# policy), so CI is the first compiler to see any Swift change, and a test
# target that doesn't build is the single most common way a PR here goes red —
# PRs #122 and #124 each burned three consecutive red runs on one compile error
# at a time. `check` reaches that error only after the engine suite starts, and
# a full run is ~11 minutes. Building just the engine and its tests reports the
# same error in ~2, which is the whole point.
compile:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: macos-26
timeout-minutes: 15
steps:
- name: Checkout blurt
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: blurt
persist-credentials: false
- name: Build the engine and its tests
working-directory: blurt
# --build-tests compiles the test targets without running them, so this is
# a typecheck of Tests/ as well as Sources/. -warnings-as-errors matches
# what check.sh passes, so a warning can't pass here and fail there. No
# xcodegen, no app build, no sanitizers, no coverage — those stay in
# `check`, and duplicating any of them here would spend the latency this
# job is meant to save.
run: swift build --build-tests -Xswiftc -warnings-as-errors
# What swift-format would change, as an applicable patch. Formatting is the
# other half of what CI is sole authority over here, and `check` can only say
# *that* a file is misformatted (`swift-format lint --strict`) — leaving the
# author to reproduce the reflow by hand, blind, with no formatter on the
# machine they're editing from. Several PRs paid for that in commits of the form
# "Format X the way swift-format wants".
#
# So: run the formatter for real and publish the diff. Read-only and advisory —
# it never fails the build (`check` already does that) and never pushes, which
# keeps this job free of a writable token while it holds PR code. Applying the
# patch stays a deliberate act by the author:
# gh run download <id> -n swift-format-patch && git apply swift-format.patch
format-patch:
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.code == 'true'
runs-on: macos-26
timeout-minutes: 10
steps:
- name: Checkout blurt
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: blurt
persist-credentials: false
- name: Format every tracked Swift file and diff the result
id: diff
working-directory: blurt
# Same file set and same tool as check.sh's lint step (xcrun swift-format,
# bundled with Xcode), so the patch is exactly what would make that step
# pass. `git diff` after an in-place format is the patch; an empty diff
# means the tree is already clean and there is nothing to publish.
run: |
set -euo pipefail
git ls-files -z -- '*.swift' | xargs -0 xcrun swift-format format --in-place
if git diff --quiet; then
echo "swift-format would change nothing." >>"$GITHUB_STEP_SUMMARY"
echo "dirty=false" >>"$GITHUB_OUTPUT"
exit 0
fi
echo "dirty=true" >>"$GITHUB_OUTPUT"
git diff >"$RUNNER_TEMP/swift-format.patch"
{
echo "### swift-format would reformat these files"
echo
echo '```console'
git diff --stat
echo '```'
echo
echo "Apply with \`git apply swift-format.patch\` from the artifact below."
echo
echo "<details><summary>Full patch</summary>"
echo
echo '```diff'
cat "$RUNNER_TEMP/swift-format.patch"
echo '```'
echo
echo "</details>"
} >>"$GITHUB_STEP_SUMMARY"
- name: Upload the patch
if: steps.diff.outputs.dirty == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: swift-format-patch
path: ${{ runner.temp }}/swift-format.patch
if-no-files-found: error
retention-days: 14
# Always-runs summary job, so a *skipped* `check` can't be mistaken for a pass.
# GitHub reports a skipped required check to branch protection as successful, so
# anything that makes `check` skip — the `changes` filter erroring, a `needs`
# failure — silently bypasses the entire macOS gate on a green-looking PR.
# This job runs `if: always()` and fails unless `check` either succeeded or was
# skipped *by the docs-only design* (changes.outputs.code == 'false').
#
# NOTE: this only protects the repo once branch protection requires `gate`
# instead of (or as well as) `check` — that's a repo settings change.
gate:
needs: [changes, check]
if: always()
runs-on: ubuntu-latest
steps:
- name: Assert the macOS gate ran or was intentionally skipped
env:
CHECK_RESULT: ${{ needs.check.result }}
CHANGES_RESULT: ${{ needs.changes.result }}
CODE_CHANGED: ${{ needs.changes.outputs.code }}
run: |
echo "changes=$CHANGES_RESULT code=$CODE_CHANGED check=$CHECK_RESULT"
if [ "$CHANGES_RESULT" != "success" ]; then
echo "error: the changes filter did not succeed, so the gate cannot be trusted" >&2
exit 1
fi
case "$CHECK_RESULT" in
success) echo "macOS gate passed" ;;
skipped)
if [ "$CODE_CHANGED" = "false" ]; then
echo "docs-only change; macOS gate intentionally skipped"
else
echo "error: check was skipped but code changed" >&2
exit 1
fi
;;
*) echo "error: macOS gate result was '$CHECK_RESULT'" >&2; exit 1 ;;
esac