fix: detect silent SSE connection drops during long tool executions #166
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: Build & Package | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| platform: | |
| description: "Target platform" | |
| required: true | |
| default: "all" | |
| type: choice | |
| options: | |
| - all | |
| - windows | |
| - macos | |
| - linux | |
| pull_request: | |
| branches: [main] | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| jobs: | |
| lint-test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Lint | |
| # TODO: fix 4 existing lint errors then remove continue-on-error | |
| continue-on-error: true | |
| run: npm run lint | |
| - name: Typecheck | |
| run: npm run typecheck | |
| - name: Unit tests | |
| run: npm run test:unit | |
| build-windows: | |
| needs: [lint-test] | |
| if: ${{ github.event_name == 'push' || inputs.platform == 'all' || inputs.platform == 'windows' }} | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Electron app | |
| run: npm run electron:build | |
| - name: Package for Windows | |
| run: npx electron-builder --win --config electron-builder.yml --publish never | |
| - name: Generate checksums | |
| shell: pwsh | |
| run: | | |
| cd release | |
| Get-ChildItem *.exe | ForEach-Object { | |
| $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() | |
| "$hash $($_.Name)" | Out-File -Append checksums-windows.sha256 -Encoding utf8 | |
| } | |
| Get-Content checksums-windows.sha256 | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-windows | |
| path: | | |
| release/*.exe | |
| release/checksums-windows.sha256 | |
| retention-days: 3 | |
| build-macos: | |
| needs: [lint-test] | |
| if: ${{ github.event_name == 'push' || inputs.platform == 'all' || inputs.platform == 'macos' }} | |
| runs-on: macos-latest | |
| env: | |
| HAS_CERT: ${{ secrets.MAC_CERT_P12_BASE64 != '' && 'true' || 'false' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Electron app | |
| run: npm run electron:build | |
| - name: Decode signing certificate | |
| env: | |
| MAC_CERT_P12_BASE64: ${{ secrets.MAC_CERT_P12_BASE64 }} | |
| MAC_CERT_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }} | |
| run: | | |
| if [ "${HAS_CERT}" != "true" ]; then | |
| echo "::error::Missing signing certificate (MAC_CERT_P12_BASE64 secret not set)" | |
| exit 1 | |
| fi | |
| printf '%s' "$MAC_CERT_P12_BASE64" | tr -d '\r\n' | base64 --decode > /tmp/cert.p12 | |
| openssl pkcs12 -in /tmp/cert.p12 -passin pass:"$MAC_CERT_PASSWORD" -noout | |
| echo "Certificate decoded and validated" | |
| - name: Package for macOS (x64 + arm64, sign only) | |
| timeout-minutes: 15 | |
| env: | |
| CSC_LINK: /tmp/cert.p12 | |
| CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }} | |
| run: npx electron-builder --mac --config electron-builder.yml --publish never | |
| - name: Verify code signature | |
| run: | | |
| APPS=$(find release -name "CodePilot.app" -maxdepth 3) | |
| if [ -z "$APPS" ]; then | |
| echo "::error::No CodePilot.app found after build" | |
| exit 1 | |
| fi | |
| FAILED=0 | |
| while IFS= read -r APP_PATH; do | |
| echo "========== Verifying: $APP_PATH ==========" | |
| codesign -dv --verbose=4 "$APP_PATH" 2>&1 | tee /tmp/codesign-info.txt || true | |
| if ! grep -q 'Authority=Developer ID Application' /tmp/codesign-info.txt; then | |
| echo "::error::$APP_PATH is NOT signed with Developer ID Application" | |
| FAILED=1 | |
| continue | |
| fi | |
| if ! grep -q 'TeamIdentifier=K9X599X9Q2' /tmp/codesign-info.txt; then | |
| echo "::error::$APP_PATH has unexpected TeamIdentifier" | |
| FAILED=1 | |
| continue | |
| fi | |
| codesign --verify --deep --strict --verbose=4 "$APP_PATH" | |
| echo "✓ $APP_PATH passed all checks" | |
| done <<< "$APPS" | |
| if [ "$FAILED" -ne 0 ]; then | |
| echo "::error::One or more .app bundles failed signature verification" | |
| exit 1 | |
| fi | |
| - name: Generate checksums | |
| run: | | |
| cd release | |
| shasum -a 256 *.dmg *.zip 2>/dev/null | tee checksums-macos.sha256 | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-macos | |
| path: | | |
| release/*.dmg | |
| release/*.zip | |
| release/checksums-macos.sha256 | |
| retention-days: 3 | |
| build-linux-x64: | |
| needs: [lint-test] | |
| if: ${{ github.event_name == 'push' || inputs.platform == 'all' || inputs.platform == 'linux' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Electron app | |
| run: npm run electron:build | |
| - name: Package for Linux (x64) | |
| run: npx electron-builder --linux --x64 --config electron-builder.yml --publish never | |
| - name: Verify Linux x64 artifacts | |
| run: | | |
| echo "=== Artifact list ===" | |
| find release -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) | sort | |
| COUNT=$(find release -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) | wc -l) | |
| if [ "$COUNT" -eq 0 ]; then | |
| echo "::error::No Linux x64 artifacts produced" | |
| exit 1 | |
| fi | |
| # Verify AppImage ELF architecture | |
| for f in release/*.AppImage; do | |
| [ -f "$f" ] || continue | |
| FILE_INFO=$(file "$f") | |
| echo "$FILE_INFO" | |
| if ! echo "$FILE_INFO" | grep -qi "x86-64\|x86_64"; then | |
| echo "::error::AppImage $f is not x64" | |
| exit 1 | |
| fi | |
| done | |
| # Verify deb architecture | |
| for f in release/*.deb; do | |
| [ -f "$f" ] || continue | |
| DEB_ARCH=$(dpkg-deb --info "$f" 2>/dev/null | grep '^ Architecture:' | awk '{print $2}') | |
| echo "deb $f arch=$DEB_ARCH" | |
| if [ "$DEB_ARCH" != "amd64" ]; then | |
| echo "::error::deb $f has wrong architecture: $DEB_ARCH (expected amd64)" | |
| exit 1 | |
| fi | |
| done | |
| # Verify rpm architecture | |
| for f in release/*.rpm; do | |
| [ -f "$f" ] || continue | |
| RPM_ARCH=$(rpm -qp --qf '%{ARCH}' "$f" 2>/dev/null || echo "unknown") | |
| echo "rpm $f arch=$RPM_ARCH" | |
| if [ "$RPM_ARCH" != "x86_64" ]; then | |
| echo "::error::rpm $f has wrong architecture: $RPM_ARCH (expected x86_64)" | |
| exit 1 | |
| fi | |
| done | |
| echo "✓ All Linux x64 artifacts verified" | |
| - name: Generate checksums | |
| run: | | |
| cd release | |
| sha256sum *.AppImage *.deb *.rpm 2>/dev/null | tee checksums-linux-x64.sha256 | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-linux-x64 | |
| path: | | |
| release/*.AppImage | |
| release/*.deb | |
| release/*.rpm | |
| release/checksums-linux-x64.sha256 | |
| retention-days: 3 | |
| build-linux-arm64: | |
| needs: [lint-test] | |
| if: ${{ github.event_name == 'push' || inputs.platform == 'all' || inputs.platform == 'linux' }} | |
| # Use a native arm64 runner — avoids fragile cross-compilation toolchain | |
| # setup that breaks across Ubuntu versions (sources.list vs deb822). | |
| # Falls back gracefully: if the runner label isn't available, the job | |
| # stays queued and doesn't block the x64 build or other platforms. | |
| runs-on: ubuntu-24.04-arm | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Electron app | |
| run: npm run electron:build | |
| - name: Package for Linux (arm64) | |
| run: npx electron-builder --linux --arm64 --config electron-builder.yml --publish never | |
| - name: Verify Linux arm64 artifacts | |
| run: | | |
| echo "=== Artifact list ===" | |
| find release -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) | sort | |
| COUNT=$(find release -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) | wc -l) | |
| if [ "$COUNT" -eq 0 ]; then | |
| echo "::error::No Linux arm64 artifacts produced" | |
| exit 1 | |
| fi | |
| # Verify AppImage ELF architecture | |
| for f in release/*.AppImage; do | |
| [ -f "$f" ] || continue | |
| FILE_INFO=$(file "$f") | |
| echo "$FILE_INFO" | |
| if ! echo "$FILE_INFO" | grep -qi "aarch64\|ARM aarch64"; then | |
| echo "::error::AppImage $f is not arm64" | |
| exit 1 | |
| fi | |
| done | |
| # Verify deb architecture | |
| for f in release/*.deb; do | |
| [ -f "$f" ] || continue | |
| DEB_ARCH=$(dpkg-deb --info "$f" 2>/dev/null | grep '^ Architecture:' | awk '{print $2}') | |
| echo "deb $f arch=$DEB_ARCH" | |
| if [ "$DEB_ARCH" != "arm64" ]; then | |
| echo "::error::deb $f has wrong architecture: $DEB_ARCH (expected arm64)" | |
| exit 1 | |
| fi | |
| done | |
| # Verify rpm architecture | |
| for f in release/*.rpm; do | |
| [ -f "$f" ] || continue | |
| RPM_ARCH=$(rpm -qp --qf '%{ARCH}' "$f" 2>/dev/null || echo "unknown") | |
| echo "rpm $f arch=$RPM_ARCH" | |
| if [ "$RPM_ARCH" != "aarch64" ]; then | |
| echo "::error::rpm $f has wrong architecture: $RPM_ARCH (expected aarch64)" | |
| exit 1 | |
| fi | |
| done | |
| echo "✓ All Linux arm64 artifacts verified" | |
| - name: Generate checksums | |
| run: | | |
| cd release | |
| sha256sum *.AppImage *.deb *.rpm 2>/dev/null | tee checksums-linux-arm64.sha256 | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-linux-arm64 | |
| path: | | |
| release/*.AppImage | |
| release/*.deb | |
| release/*.rpm | |
| release/checksums-linux-arm64.sha256 | |
| retention-days: 3 | |
| release: | |
| if: ${{ always() && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(needs.*.result, 'cancelled') }} | |
| needs: [build-windows, build-macos, build-linux-x64, build-linux-arm64] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: List artifacts | |
| run: find artifacts -type f | sort | |
| - name: Get version and previous tag | |
| id: meta | |
| run: | | |
| VERSION=${GITHUB_REF_NAME#v} | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| PREV_TAG=$(git tag --sort=-creatordate | grep '^v' | sed -n '2p' || echo "") | |
| echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| PREV_TAG="${{ steps.meta.outputs.prev_tag }}" | |
| if [ -n "$PREV_TAG" ]; then | |
| RANGE="${PREV_TAG}..HEAD" | |
| else | |
| RANGE="HEAD" | |
| fi | |
| { | |
| echo "changelog<<CHANGELOG_EOF" | |
| git log "$RANGE" --pretty=format:'| `%h` | %s |' | |
| echo "" | |
| echo "CHANGELOG_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Create release and upload assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ steps.meta.outputs.version }}" | |
| PREV_TAG="${{ steps.meta.outputs.prev_tag }}" | |
| cat > release-notes.md << 'NOTES_EOF' | |
| ## Downloads | |
| **Windows** | |
| - **CodePilot Setup ${{ steps.meta.outputs.version }}.exe** — Windows installer (x64 + arm64) | |
| **macOS** | |
| - **CodePilot-${{ steps.meta.outputs.version }}-arm64.dmg** — macOS Apple Silicon (M1/M2/M3/M4) | |
| - **CodePilot-${{ steps.meta.outputs.version }}-x64.dmg** — macOS Intel | |
| **Linux** | |
| - **AppImage / deb / rpm** — x64 and arm64 | |
| ## Installation | |
| **Windows**: Download the exe installer, double-click to install. | |
| **macOS**: | |
| 1. Download the DMG for your chip architecture | |
| 2. Open the DMG, drag CodePilot into Applications | |
| 3. If you see a security prompt on first launch, go to **System Settings > Privacy & Security** and click "Open Anyway" | |
| 4. Configure your Anthropic API Key in Settings | |
| **Linux**: Run the AppImage directly, or install via deb/rpm package. | |
| ## Requirements | |
| - Windows 10+ / macOS 12.0+ / Linux (glibc 2.31+) | |
| - Anthropic API Key or `ANTHROPIC_API_KEY` environment variable | |
| - Claude Code CLI recommended for code features | |
| ## Changelog | |
| NOTES_EOF | |
| if [ -n "$PREV_TAG" ]; then | |
| echo "" >> release-notes.md | |
| echo "| Commit | Description |" >> release-notes.md | |
| echo "|--------|-------------|" >> release-notes.md | |
| cat >> release-notes.md << 'CHANGELOG_INLINE_EOF' | |
| ${{ steps.changelog.outputs.changelog }} | |
| CHANGELOG_INLINE_EOF | |
| fi | |
| # Merge per-platform checksums into a single file | |
| echo "## SHA-256 Checksums" > artifacts/SHA256SUMS.txt | |
| echo "" >> artifacts/SHA256SUMS.txt | |
| for cs in artifacts/checksums-*.sha256; do | |
| [ -f "$cs" ] || continue | |
| cat "$cs" >> artifacts/SHA256SUMS.txt | |
| done | |
| echo "=== Combined checksums ===" | |
| cat artifacts/SHA256SUMS.txt | |
| # Collect release files: installers + checksum file (no blockmap / update metadata) | |
| FILES=() | |
| while IFS= read -r f; do | |
| FILES+=("$f") | |
| done < <(find artifacts -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) | sort) | |
| FILES+=("artifacts/SHA256SUMS.txt") | |
| gh release create "${GITHUB_REF_NAME}" \ | |
| --title "CodePilot v${VERSION}" \ | |
| --notes-file release-notes.md \ | |
| --latest \ | |
| "${FILES[@]}" |