CI #229
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| package_only: | |
| description: Run package tests and coverage without application audits or distribution builds | |
| type: boolean | |
| default: false | |
| ui_group: | |
| description: Diagnostic UI group, or performance for opt-in benchmarks on the latest macOS; empty runs functional | |
| acceptance | |
| type: string | |
| default: '' | |
| ui_test: | |
| description: One method within the diagnostic group; leave empty for the whole group | |
| type: string | |
| default: '' | |
| ui_profile_startup: | |
| description: Sample replacement-process startup during diagnosis; not a performance acceptance run | |
| type: boolean | |
| default: false | |
| ui_runtime: | |
| description: Runtime for a diagnostic UI group | |
| type: choice | |
| options: ['14', '15', '26', '27'] | |
| default: '14' | |
| run-name: ${{ inputs.ui_group == 'performance' && 'Performance benchmarks (latest macOS)' || inputs.ui_profile_startup && | |
| format('Startup profile ({0}, macOS {1})', inputs.ui_group, inputs.ui_runtime) || inputs.ui_group && format('UI diagnosis | |
| ({0}, macOS {1})', inputs.ui_group, inputs.ui_runtime) || inputs.package_only && 'Package tests' || 'CI' }} | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.ref }}${{ inputs.ui_group && format('-diagnostic-{0}-{1}', inputs.ui_runtime, inputs.ui_group) || '' | |
| }} | |
| cancel-in-progress: true | |
| jobs: | |
| runtime-matrix: | |
| name: Select deployed macOS runtimes | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 2 | |
| outputs: | |
| runtimes: ${{ steps.matrix.outputs.runtimes }} | |
| package: ${{ steps.matrix.outputs.package }} | |
| ui: ${{ steps.matrix.outputs.ui }} | |
| application: ${{ steps.matrix.outputs.application }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Test runtime selection | |
| run: Scripts/test-ci-runtime-matrix.sh | |
| - name: Check macOS 27 deployment | |
| id: matrix | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| MACOS_14_RUNNER: ${{ vars.MACOS_14_RUNNER || 'macos-14' }} | |
| UI_GROUP: ${{ inputs.ui_group }} | |
| UI_TEST: ${{ inputs.ui_test }} | |
| UI_PROFILE_STARTUP: ${{ inputs.ui_profile_startup }} | |
| PACKAGE_ONLY: ${{ inputs.package_only }} | |
| DIAGNOSTIC_UI_RUNTIME: ${{ inputs.ui_group && inputs.ui_runtime || '0' }} | |
| run: | | |
| [[ -z "$UI_TEST" && "$UI_PROFILE_STARTUP" != true || -n "$UI_GROUP" ]] | |
| [[ "$UI_PROFILE_STARTUP" != true || "$UI_GROUP" == lifecycle ]] | |
| if [[ -n "$UI_GROUP" ]]; then | |
| [[ "$PACKAGE_ONLY" != true ]] | |
| Scripts/check-ui-test-inventory.sh | |
| fi | |
| gh api repos/actions/runner-images/releases/tags/xcode-27-arm64%2F20260907.0173 | Scripts/ci-runtime-matrix.sh | |
| lint: | |
| name: Lint | |
| if: ${{ !inputs.ui_group }} | |
| runs-on: macos-26 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 | |
| with: | |
| version: 2026.9.3 | |
| - name: Select Xcode | |
| run: Scripts/select-xcode.sh | |
| - name: Run every hook | |
| run: just lint | |
| - name: Verify native test execution guards | |
| run: Scripts/test-desktop-guard.sh | |
| - name: Verify local runner isolation and resource limits | |
| run: bash Scripts/test-local-ui-runner.sh | |
| - name: Verify UI group coverage | |
| run: | | |
| Scripts/check-ui-test-inventory.sh | |
| Scripts/test-ui-test-inventory.sh | |
| - name: Verify deferred render checks | |
| run: Scripts/test-render-capture.sh | |
| package-compatibility: | |
| name: Package (${{ matrix.name }}, ${{ matrix.mode }}) | |
| needs: runtime-matrix | |
| if: ${{ !inputs.ui_group }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 10 | |
| env: | |
| TOKEN_MENU_BAR_RENDER_CAPTURE_ONLY: 1 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.runtime-matrix.outputs.package) }} | |
| steps: | |
| - name: Verify runtime | |
| env: | |
| EXPECTED_MACOS_MAJOR: ${{ matrix.runtime-major }} | |
| run: | | |
| version="$(sw_vers -productVersion)" | |
| [[ "${version%%.*}" == "$EXPECTED_MACOS_MAJOR" ]] || { echo "::error::Unexpected macOS $version"; exit 1; } | |
| echo "Runtime: macOS $version" >> "$GITHUB_STEP_SUMMARY" | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 | |
| with: | |
| version: 2026.9.3 | |
| - name: Select Xcode | |
| id: xcode | |
| env: | |
| MIN_XCODE_MAJOR: ${{ matrix.minimum-xcode }} | |
| EXACT_XCODE_MAJOR: ${{ matrix.minimum-xcode }} | |
| run: | | |
| Scripts/select-xcode.sh | |
| version="$(xcodebuild -version | tr '\n' '-')" | |
| echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT" | |
| - name: Restore SwiftPM build and input timestamps | |
| uses: irgaly/xcode-cache@4141f139f00e335c6e1031fb93e667181f86146f # v1.9.2 | |
| with: | |
| deriveddata-directory: .build | |
| key: spm-v4-${{ matrix.mode }}-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache | |
| }}-${{ hashFiles('Package.swift', 'justfile', 'Scripts/coverage.sh') }}-${{ github.sha }} | |
| restore-keys: | | |
| spm-v4-${{ matrix.mode }}-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift', 'justfile', 'Scripts/coverage.sh') }}- | |
| use-default-mtime-targets: false | |
| restore-mtime-targets: | | |
| Package.swift | |
| Sources/**/* | |
| Tests/**/* | |
| - name: Check toolchain | |
| id: toolchain | |
| run: Scripts/check-toolchain.sh | |
| - name: Test without native presentation | |
| if: matrix.mode == 'nonpresenting' | |
| env: | |
| TOKEN_MENU_BAR_RENDER_ARTIFACTS: ${{ runner.temp }}/settings-render | |
| run: just test | |
| - name: Test native presentation | |
| if: matrix.mode == 'native' | |
| env: | |
| TOKEN_MENU_BAR_RENDER_ARTIFACTS: ${{ runner.temp }}/settings-render | |
| run: just test-native | |
| - name: Retain mock Settings renders | |
| if: ${{ !cancelled() && steps.toolchain.outcome == 'success' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: rendered-package-${{ matrix.runtime-major }}-${{ matrix.mode }} | |
| path: ${{ runner.temp }}/settings-render | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Retain native test crash reports | |
| if: ${{ failure() && matrix.mode == 'native' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: package-crashes-${{ matrix.runtime-major }} | |
| path: ~/Library/Logs/DiagnosticReports/*.ips | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| coverage: | |
| name: Tests and coverage | |
| if: ${{ !inputs.ui_group }} | |
| runs-on: macos-26 | |
| timeout-minutes: 10 | |
| env: | |
| TOKEN_MENU_BAR_RENDER_CAPTURE_ONLY: 1 | |
| steps: | |
| - name: Verify runtime | |
| run: | | |
| version="$(sw_vers -productVersion)" | |
| [[ "${version%%.*}" == 26 ]] || { echo "::error::Unexpected macOS $version"; exit 1; } | |
| echo "Runtime: macOS $version" >> "$GITHUB_STEP_SUMMARY" | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 | |
| with: | |
| version: 2026.9.3 | |
| - name: Select Xcode | |
| id: xcode | |
| run: | | |
| Scripts/select-xcode.sh | |
| version="$(xcodebuild -version | tr '\n' '-')" | |
| echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT" | |
| - name: Restore SwiftPM build and input timestamps | |
| uses: irgaly/xcode-cache@4141f139f00e335c6e1031fb93e667181f86146f # v1.9.2 | |
| with: | |
| deriveddata-directory: .build | |
| key: spm-v4-coverage-${{ runner.os }}-${{ runner.arch }}-macos-26-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift', | |
| 'justfile', 'Scripts/coverage.sh') }}-${{ github.sha }} | |
| restore-keys: | | |
| spm-v4-coverage-${{ runner.os }}-${{ runner.arch }}-macos-26-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift', 'justfile', 'Scripts/coverage.sh') }}- | |
| use-default-mtime-targets: false | |
| restore-mtime-targets: | | |
| Package.swift | |
| Sources/**/* | |
| Tests/**/* | |
| - name: Check toolchain | |
| id: toolchain | |
| run: Scripts/check-toolchain.sh | |
| - name: Tests with coverage gate | |
| if: ${{ !cancelled() && steps.toolchain.outcome == 'success' }} | |
| env: | |
| TOKEN_MENU_BAR_RENDER_ARTIFACTS: ${{ runner.temp }}/settings-render | |
| run: just coverage | |
| - name: Retain mock Settings renders | |
| if: ${{ !cancelled() && steps.toolchain.outcome == 'success' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: rendered-package-26-native | |
| path: ${{ runner.temp }}/settings-render | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Retain native test crash reports | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: package-crashes-26 | |
| path: ~/Library/Logs/DiagnosticReports/*.ips | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| application-ui: | |
| name: Application UI (${{ matrix.name }}) | |
| permissions: | |
| actions: read | |
| contents: read | |
| needs: runtime-matrix | |
| if: ${{ github.event_name != 'workflow_dispatch' || !inputs.package_only }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.runtime-matrix.outputs.ui) }} | |
| # https://github.com/rhysd/actionlint/blob/v1.7.12/rule_workflow_call.go | |
| uses: ./.github/workflows/ui.yml # zizmor: ignore[self-repository] -- actionlint 1.7.12 rejects $/ workflow calls | |
| with: | |
| runner: ${{ matrix.runner }} | |
| runtime-major: ${{ matrix.runtime-major }} | |
| minimum-xcode: ${{ matrix.minimum-xcode }} | |
| group: ${{ inputs.ui_group || '' }} | |
| test: ${{ inputs.ui_test || '' }} | |
| profile-startup: ${{ inputs.ui_profile_startup || false }} | |
| rendered-text: | |
| name: Rendered text | |
| permissions: | |
| actions: read | |
| contents: read | |
| needs: [runtime-matrix, package-compatibility, coverage, application-ui] | |
| if: ${{ !cancelled() && needs.runtime-matrix.result == 'success' && !inputs.ui_group }} | |
| runs-on: macos-26 | |
| timeout-minutes: 10 | |
| env: | |
| RUNTIMES: ${{ join(fromJSON(needs.runtime-matrix.outputs.runtimes), ' ') }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install the pinned OCR engine | |
| run: Scripts/install-ocr.sh | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: rendered-* | |
| github-token: ${{ github.token }} | |
| path: .build/rendered | |
| - name: Check every package render | |
| run: | | |
| result=0 | |
| for runtime in $RUNTIMES; do | |
| for mode in nonpresenting native; do | |
| directory=".build/rendered/rendered-package-$runtime-$mode" | |
| Scripts/check-rendered-text.sh "$directory" 16 || result=1 | |
| fixtures=("$directory"/run.*/verifier-present.png) | |
| [[ "${#fixtures[@]}" == 1 ]] | |
| Scripts/test-rendered-text.sh "${fixtures[0]}" || result=1 | |
| done | |
| done | |
| exit "$result" | |
| - name: Check every long-text UI group | |
| if: ${{ !cancelled() && (github.event_name != 'workflow_dispatch' || !inputs.package_only) }} | |
| run: | | |
| result=0 | |
| for runtime in $RUNTIMES; do | |
| while IFS= read -r group; do | |
| Scripts/check-rendered-text.sh ".build/rendered/rendered-ui-$runtime-$group" 1 || result=1 | |
| done < <(jq -r 'keys[] | select(startswith("text-"))' .github/ui-test-groups.json) | |
| done | |
| exit "$result" | |
| application: | |
| name: >- | |
| ${{ matrix.runtime-major == 26 && format('Build app flavors ({0}, {1})', matrix.scheme, matrix.configuration) || format('Build | |
| {0} ({1})', matrix.scheme, matrix.name) }} | |
| needs: runtime-matrix | |
| if: ${{ github.event_name != 'workflow_dispatch' || (!inputs.package_only && !inputs.ui_group) }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.runtime-matrix.outputs.application) }} | |
| steps: | |
| - name: Verify runtime | |
| env: | |
| EXPECTED_MACOS_MAJOR: ${{ matrix.runtime-major }} | |
| run: | | |
| version="$(sw_vers -productVersion)" | |
| [[ "${version%%.*}" == "$EXPECTED_MACOS_MAJOR" ]] || { echo "::error::Unexpected macOS $version"; exit 1; } | |
| echo "Runtime: macOS $version" >> "$GITHUB_STEP_SUMMARY" | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 | |
| with: | |
| version: 2026.9.3 | |
| - name: Select Xcode | |
| id: xcode | |
| env: | |
| MIN_XCODE_MAJOR: ${{ matrix.minimum-xcode }} | |
| EXACT_XCODE_MAJOR: ${{ matrix.minimum-xcode }} | |
| run: | | |
| Scripts/select-xcode.sh | |
| version="$(xcodebuild -version | tr '\n' '-')" | |
| echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT" | |
| - name: Cache Xcode packages | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: .build/xcode-packages | |
| key: xcode-spm-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift', | |
| 'App/project.yml') }} | |
| - name: Generate project | |
| run: just xcode | |
| - name: Restore distribution build | |
| id: distribution-cache | |
| uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| .build/app-derived/Build | |
| .build/app-derived/ModuleCache.noindex | |
| .build/app-derived/SDKStatCaches.noindex | |
| .build/app-derived/CompilationCache.noindex | |
| key: distribution-v1-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ | |
| matrix.configuration }}-${{ hashFiles('Package.swift', 'App/project.yml') }}-${{ hashFiles('Sources/**', 'App/**') | |
| }} | |
| restore-keys: | | |
| distribution-v1-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ matrix.configuration }}-${{ hashFiles('Package.swift', 'App/project.yml') }}- | |
| - name: Build | |
| env: | |
| SCHEME: ${{ matrix.scheme }} | |
| CONFIGURATION: ${{ matrix.configuration }} | |
| run: | | |
| set -o pipefail | |
| mkdir -p .build | |
| xcodebuild -project App/TokenMenuBar.xcodeproj -scheme "$SCHEME" -configuration "$CONFIGURATION" \ | |
| -destination 'platform=macOS' -derivedDataPath .build/app-derived -jobs 2 \ | |
| -clonedSourcePackagesDirPath .build/xcode-packages CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY="" \ | |
| SPARKLE_PUBLIC_ED_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= build \ | |
| 2>&1 | tee .build/distribution-build.log | tail -30 | |
| - name: Save distribution build | |
| if: steps.distribution-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| .build/app-derived/Build | |
| .build/app-derived/ModuleCache.noindex | |
| .build/app-derived/SDKStatCaches.noindex | |
| .build/app-derived/CompilationCache.noindex | |
| key: ${{ steps.distribution-cache.outputs.cache-primary-key }} | |
| - name: Retain distribution build log | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: distribution-${{ matrix.configuration }}-${{ matrix.runner }} | |
| path: .build/distribution-build.log | |
| retention-days: 1 | |
| - name: Verify distribution settings | |
| env: | |
| CONFIGURATION: ${{ matrix.configuration }} | |
| DISTRIBUTION: ${{ matrix.distribution }} | |
| CONDITION: ${{ matrix.condition }} | |
| SANDBOX: ${{ matrix.sandbox }} | |
| ENTITLEMENTS: ${{ matrix.entitlements }} | |
| TARGET: ${{ matrix.target }} | |
| run: Scripts/verify-build-settings.sh "$TARGET" "$CONFIGURATION" "$DISTRIBUTION" "$CONDITION" "$SANDBOX" "$ENTITLEMENTS" | |
| - name: Verify every shipped deployment target | |
| env: | |
| DISTRIBUTION: ${{ matrix.distribution }} | |
| UPDATER: ${{ matrix.updater }} | |
| run: | | |
| app="$(find .build/app-derived/Build/Products -path '*/Token Menu Bar.app' -type d | head -1)" | |
| test -n "$app" | |
| Scripts/verify-deployment-targets.sh "$app" 14.0 | |
| Scripts/verify-app-bundle.sh "$app" "$DISTRIBUTION" "$UPDATER" | |
| - name: Package the macOS 14 smoke artifact | |
| if: matrix.runner == 'macos-26' && matrix.scheme == 'TokenMenuBar-Direct' | |
| run: | | |
| app="$(find .build/app-derived/Build/Products -path '*/Token Menu Bar.app' -type d | head -1)" | |
| codesign --force --deep --sign - "$app" | |
| ditto -c -k --keepParent "$app" .build/token-menu-bar-macos-14-smoke.zip | |
| - name: Upload the macOS 14 smoke artifact | |
| if: matrix.runner == 'macos-26' && matrix.scheme == 'TokenMenuBar-Direct' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: token-menu-bar-macos-14-smoke | |
| path: .build/token-menu-bar-macos-14-smoke.zip | |
| if-no-files-found: error | |
| retention-days: 1 | |
| macos-14-runtime: | |
| name: Release smoke (macOS 14 runtime) | |
| permissions: | |
| actions: read | |
| contents: read | |
| needs: [runtime-matrix, application] | |
| if: ${{ !cancelled() && needs.runtime-matrix.result == 'success' && (github.event_name != 'workflow_dispatch' || (!inputs.package_only | |
| && !inputs.ui_group)) }} | |
| runs-on: ${{ vars.MACOS_14_RUNTIME_RUNNER || 'macos-14' }} | |
| steps: | |
| - name: Verify runtime | |
| run: | | |
| version="$(sw_vers -productVersion)" | |
| [[ "${version%%.*}" == 14 ]] || { echo "::error::Unexpected macOS $version"; exit 1; } | |
| echo "Runtime: macOS $version" >> "$GITHUB_STEP_SUMMARY" | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: token-menu-bar-macos-14-smoke | |
| path: smoke | |
| github-token: ${{ github.token }} | |
| - name: Launch with isolated data | |
| run: | | |
| ditto -x -k smoke/token-menu-bar-macos-14-smoke.zip smoke/app | |
| executable="smoke/app/Token Menu Bar.app/Contents/MacOS/Token Menu Bar" | |
| runtime="$PWD/smoke/runtime" | |
| session="macos-14-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| support="$runtime/token-menu-bar-verify-$session" | |
| ready="$support/snapshots-demo.json" | |
| mkdir -p "$support" | |
| TOKEN_MENU_BAR_VERIFY_SESSION="$session" TOKEN_MENU_BAR_VERIFY_SUPPORT_DIRECTORY="$support" \ | |
| "$executable" --verify-ui >smoke/launch.log 2>&1 & | |
| pid=$! | |
| for _ in {1..80}; do | |
| if grep -Fq '"claude"' "$ready" 2>/dev/null && grep -Fq '"codex"' "$ready" 2>/dev/null; then | |
| break | |
| fi | |
| if ! kill -0 "$pid" 2>/dev/null; then | |
| cat smoke/launch.log | |
| exit 1 | |
| fi | |
| sleep 0.25 | |
| done | |
| if ! grep -Fq '"claude"' "$ready" 2>/dev/null || ! grep -Fq '"codex"' "$ready" 2>/dev/null; then | |
| cat smoke/launch.log | |
| test ! -f "$support/log.txt" || cat "$support/log.txt" | |
| kill "$pid" 2>/dev/null || true | |
| wait "$pid" || true | |
| exit 1 | |
| fi | |
| kill "$pid" 2>/dev/null || true | |
| wait "$pid" || true |