From eb90bedaf4c92ad1cc31d594ac963113d8a59dd9 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 13 May 2026 08:14:13 -0500 Subject: [PATCH] fix(ci): pin Xcode 26.x, drop phantom VoiceLog, harden Batcher timing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI run 25799596445 failed on 6 SPM packages + the App target. Root cause was a toolchain skew between local dev (Swift 6.3.2 / Xcode 26) and CI (Swift 6.0 / Xcode 16): - mlx-audio-swift v0.1.2 ships swift-tools-version:6.2 which Swift 6.0 cannot parse, breaking Voice + Harness + the App target with "Dependencies could not be resolved because 'mlx-audio-swift' contains incompatible tools version (6.2.0)". - MCP swift-sdk 0.12.0 uses the new `withThrowingTaskGroup` overload (no `of:` parameter) that only exists in Swift 6.2+. - AgentCore tests use `@MainActor` XCTestCase with `await super.setUp()`, a pattern Swift 6.0 rejects but Swift 6.1+ accepts. The macos-15 runner already ships Xcode 26.3 / 26.2 / 26.1 / 26.0 alongside 16.x, so the fix is to switch the Xcode select. Fallback to Xcode_26.app preserves robustness against patch-level shuffles. Also: - Drop `VoiceLog` from the SPM matrix. No `packages/VoiceLog` directory exists; the job failed with `chdir error: No such file or directory`. Voice-log work is parked under v1.1. - Bump BatcherTests `waitForDrain` from 80ms to 250ms. The batcher window is 33ms; 80ms was tight enough that macos-15 dropped test_tokenDeltaConcat with sendCount=0 because the scheduled drain hadn't completed its actor + main-actor hop before the test resumed. 250ms (~7.5× the window) survives CI jitter; verified locally — all five Batcher tests pass in 1.27s. Refs: CI run 25799596445. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 24 +++++++++++++------ .../Bus/Tests/BusTests/BatcherTests.swift | 21 +++++++++++----- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 927e36e..8343ff4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,6 +122,9 @@ jobs: strategy: fail-fast: false matrix: + # VoiceLog removed: no `packages/VoiceLog` directory exists in the + # repo. The matrix entry was a phantom that failed with `chdir error: + # No such file or directory`. Voice-log work tracked under v1.1. package: - AgentCore - Bus @@ -136,20 +139,25 @@ jobs: - Shell - Vision - Voice - - VoiceLog steps: - uses: actions/checkout@v4 + # Project targets macOS 26 Tahoe + Swift 6.2/6.3. Xcode 16 (Swift 6.0) + # cannot parse `mlx-audio-swift` v0.1.2's `swift-tools-version:6.2`, + # lacks the `withThrowingTaskGroup` overload used by MCP `swift-sdk` + # 0.12.0, and rejects `@MainActor` XCTestCase `super.setUp()` patterns + # that Swift 6.1+ accepts. Pin to the newest Xcode 26.x on macos-15. + # See actions/runner-images macos-15 readme for installed Xcode list. - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.3.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode_26.app/Contents/Developer - name: Show Swift toolchain run: | swift --version xcodebuild -version # Download the production webview bundle so Bus + downstream packages - # (AgentCore / Harness / MCP / Voice / VoiceLog) can mount a real - # WKWebView with a working JS bridge. Without this, BusTests fail - # with `Error: bus not mounted` because index.html + the JS bundle - # are missing from App/Resources/webview/. + # (AgentCore / Harness / MCP / Voice) can mount a real WKWebView with + # a working JS bridge. Without this, BusTests fail with `Error: bus + # not mounted` because index.html + the JS bundle are missing from + # App/Resources/webview/. - name: Download webview resources uses: actions/download-artifact@v4 with: @@ -173,8 +181,10 @@ jobs: needs: [boundary-gates, webview] steps: - uses: actions/checkout@v4 + # Same Xcode 26.x pin as swift-packages — Xcode 16 cannot resolve + # mlx-audio-swift's swift-tools-version:6.2 manifest. - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.3.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode_26.app/Contents/Developer - name: Install xcodegen run: brew install xcodegen # Reuse the webview build from the upstream job rather than rebuilding. diff --git a/packages/Bus/Tests/BusTests/BatcherTests.swift b/packages/Bus/Tests/BusTests/BatcherTests.swift index 32c9e2a..991078d 100644 --- a/packages/Bus/Tests/BusTests/BatcherTests.swift +++ b/packages/Bus/Tests/BusTests/BatcherTests.swift @@ -3,7 +3,13 @@ import XCTest /// Exercises `OutboundBatcher` behaviour against a recording fake sink. All /// timing assertions are designed to survive CI jitter — the batcher's nominal -/// window is 33ms; tests wait ≥60ms (≈2x) before asserting. +/// window is 33ms; tests wait 250ms (≈7.5x) before asserting. The earlier +/// 80ms wait was tight enough that macOS-15 GitHub Actions runners (cold +/// build + cold WKWebView mount sharing the box) intermittently dropped +/// `test_tokenDeltaConcat` with sendCount=0 — the 33ms `Task.sleep` had not +/// yet hopped back through the batcher actor + main actor by the time the +/// test resumed. 250ms gives the scheduled drain ample time without making +/// the suite noticeably slower (the four affected tests cost ~1s total). /// /// The fake is `@MainActor`-isolated because `BusOutbound` does not cross /// actor boundaries by default under Swift 6 strict concurrency, and the @@ -21,13 +27,16 @@ final class BatcherTests: XCTestCase { } } - // A small drain-wait helper. The batcher window is 33ms; wait 80ms to let - // the scheduled drain fire and propagate the main-actor hop. - // `@MainActor` because each test is main-actor-isolated and Swift 6 strict - // concurrency disallows sending `self` across a nonisolated boundary. + // A small drain-wait helper. The batcher window is 33ms; wait 250ms to + // let the scheduled drain fire and propagate the main-actor hop. The + // earlier 80ms wait was flaky on macos-15 CI runners (cold builds racing + // a cold WKWebView mount on the same box) — see commit message + doc + // comment above. `@MainActor` because each test is main-actor-isolated + // and Swift 6 strict concurrency disallows sending `self` across a + // nonisolated boundary. @MainActor private func waitForDrain() async { - try? await Task.sleep(for: .milliseconds(80)) + try? await Task.sleep(for: .milliseconds(250)) } // MARK: - Tests -- 2.54.0