Fix AirPods dictation: gate recording on mic liveness, and recover the tail #588
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: 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 |