Skip to content

fix(launcher): capture startup crashes to launcher.log + dump in CI #3

fix(launcher): capture startup crashes to launcher.log + dump in CI

fix(launcher): capture startup crashes to launcher.log + dump in CI #3

# =============================================================================
# Build panelagent.exe on tag push and attach it to a GitHub Release.
# =============================================================================
# Trigger: push a tag matching v* (e.g. v1.0.0). The workflow builds the
# static frontend, bundles it with the Python backend via PyInstaller on a
# Windows runner, and uploads panelagent.exe to a Release for that tag.
# =============================================================================
name: Build Windows exe
on:
push:
tags:
- "v*"
workflow_dispatch: # allow manual runs from the Actions tab
permissions:
contents: write # required to create the release and upload the asset
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install backend deps + PyInstaller
run: |
python -m pip install --upgrade pip
pip install -r backend/requirements.txt
pip install pyinstaller
- name: Build static frontend
env:
NEXT_PUBLIC_API_URL: "http://127.0.0.1:8000"
run: |
cd frontend
npm install
npm run build
if (-Not (Test-Path "out/index.html")) { throw "next build did not produce out/index.html" }
shell: pwsh
- name: Build exe
run: |
python -m PyInstaller panelagent.spec --noconfirm --clean
if (-Not (Test-Path "dist/panelagent.exe")) { throw "PyInstaller did not produce dist/panelagent.exe" }
shell: pwsh
- name: Smoke test (start exe + poll health endpoint + dump log + kill)
continue-on-error: true # don't block the release upload if the CI env can't reach localhost
run: |
Write-Host "Launching dist\panelagent.exe..."
$p = Start-Process -FilePath "dist\panelagent.exe" -PassThru
# PyInstaller one-file cold start: unpack bundle + import pandas/scipy
# can take 30-60s on first run. Poll up to 120s.
$ok = $false
for ($i = 0; $i -lt 24; $i++) {
Start-Sleep -Seconds 5
try {
$resp = Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/v1/health" -UseBasicParsing -TimeoutSec 5
if ($resp.StatusCode -eq 200) {
Write-Host "Health check OK after $($i*5)s: $($resp.Content)"
$ok = $true
break
}
} catch {
Write-Host "Waiting... ($($_.Exception.Message.Split([Environment]::NewLine)[0]))"
}
}
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# If it failed, dump the launcher log so the cause is visible in CI.
$logPath = "$env:USERPROFILE\.panelagent\launcher.log"
Write-Host "Launcher log: $logPath"
if (Test-Path $logPath) {
Write-Host "===== launcher.log ====="
Get-Content $logPath | ForEach-Object { Write-Host $_ }
Write-Host "===== end launcher.log ====="
} else {
Write-Host "No launcher.log found (exe may have crashed before logging was set up)."
}
if (-not $ok) {
Write-Host "::warning::Smoke test could not reach health endpoint within 120s. See launcher.log above. The exe will still be uploaded."
}
shell: pwsh
- name: Get version from tag
if: startsWith(github.ref, 'refs/tags/')
id: version
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $env:GITHUB_OUTPUT
shell: pwsh
- name: Create / update Release and upload asset
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag || github.ref_name }}
name: PanelAgent ${{ steps.version.outputs.tag || 'dev' }}
files: dist/panelagent.exe
generate_release_notes: true
fail_on_unmatched_files: true