Skip to content

fix: .bat files had Unix-style /dev/null; add CI lint #9

fix: .bat files had Unix-style /dev/null; add CI lint

fix: .bat files had Unix-style /dev/null; add CI lint #9

Workflow file for this run

name: Windows smoke test
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
smoke:
name: start.bat end-to-end on Windows
runs-on: windows-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install dependencies
shell: pwsh
run: npm ci
- name: Lint .bat launchers
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$bats = @("start.bat", "stop.bat")
$failed = $false
foreach ($bat in $bats) {
$bytes = [System.IO.File]::ReadAllBytes($bat)
$text = [System.Text.Encoding]::ASCII.GetString($bytes)
# 1. Pure ASCII (no UTF-8 multi-byte sequences that would
# be misread under Chinese Windows code page 936).
foreach ($b in $bytes) {
if ($b -gt 0x7f) {
Write-Error "$bat contains non-ASCII byte 0x$("{0:X2}" -f $b) — cmd.exe on zh-CN will fail to parse it"
$failed = $true
break
}
}
# 2. CRLF line endings — LF-only .bat files misbehave on some
# cmd.exe versions.
if ($text -notmatch "`r`n") {
Write-Error "$bat has no CRLF line endings"
$failed = $true
}
# 3. No Unix-style /dev/null redirection — cmd.exe would create
# an actual file named 'null' under a 'dev' directory.
if ($text -match "/dev/null") {
Write-Error "$bat contains Unix-style '/dev/null' — use 'NUL' instead"
$failed = $true
}
}
if ($failed) { exit 1 }
Write-Host "All .bat files passed ASCII / CRLF / NUL lint."
- name: Run start.bat end-to-end
shell: cmd
run: |
start.bat
- name: Stop agent started by start.bat
shell: pwsh
run: |
$pidFile = "runtime/agent/agent.pid"
if (Test-Path $pidFile) {
$agentPid = (Get-Content $pidFile -Raw).Trim()
try {
Stop-Process -Id $agentPid -Force -ErrorAction Stop
Write-Host "Stopped start.bat agent PID $agentPid"
} catch {
Write-Host "start.bat agent PID $agentPid already gone"
}
Remove-Item -Force -ErrorAction SilentlyContinue $pidFile, "runtime/agent/agent.port"
}
- name: Start agent via start-local-agent.ps1 (foreground)
shell: pwsh
run: |
$ErrorActionPreference = "Continue"
Write-Host "Running start-local-agent.ps1 in foreground to surface errors..."
& "$env:GITHUB_WORKSPACE\scripts\dev\start-local-agent.ps1"
Write-Host "start-local-agent.ps1 exit code: $LASTEXITCODE"
- name: Wait for agent to be ready
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$portFile = "runtime/agent/agent.port"
$port = $null
for ($i = 0; $i -lt 90; $i++) {
if (Test-Path $portFile) {
$port = (Get-Content $portFile -Raw).Trim()
if ($port) { break }
}
Start-Sleep -Seconds 2
}
if (-not $port) {
Write-Error "agent.port file never appeared — start.bat failed to start the agent"
exit 1
}
Write-Host "Agent port: $port"
"AGENT_PORT=$port" | Out-File -FilePath $env:GITHUB_ENV -Append
$url = "http://127.0.0.1:$port/api/config"
for ($i = 0; $i -lt 60; $i++) {
try {
$resp = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 3
if ($resp.StatusCode -eq 200) {
Write-Host "GET /api/config -> 200 OK"
Write-Host $resp.Content
exit 0
}
} catch {
Start-Sleep -Seconds 2
}
}
Write-Error "Agent never responded on /api/config within 2 minutes"
exit 1
- name: Smoke test /desktop route
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$url = "http://127.0.0.1:$env:AGENT_PORT/desktop"
$resp = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 10
if ($resp.StatusCode -ne 200) {
Write-Error "/desktop returned $($resp.StatusCode)"
exit 1
}
Write-Host "/desktop -> 200 OK ($($resp.Content.Length) bytes)"
- name: Smoke test login (POST /api/pair)
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$tokenFile = "runtime/agent/app_data/pairing-token.txt"
if (-not (Test-Path $tokenFile)) {
Write-Error "pairing-token.txt missing — agent never initialized session store"
exit 1
}
$token = (Get-Content $tokenFile -Raw).Trim()
Write-Host "Pairing token: $token"
$url = "http://127.0.0.1:$env:AGENT_PORT/api/pair"
# 1. Wrong token -> 401
try {
$bad = Invoke-WebRequest -UseBasicParsing -Uri $url -Method POST `
-ContentType "application/json" `
-Body '{"token":"wrong-token"}' -TimeoutSec 5
Write-Error "Wrong token should have failed, got $($bad.StatusCode)"
exit 1
} catch {
if ($_.Exception.Response.StatusCode.value__ -ne 401) {
Write-Error "Wrong token expected 401, got $($_.Exception.Response.StatusCode.value__)"
exit 1
}
Write-Host "Wrong token -> 401 (as expected)"
}
# 2. Correct token -> 200 { ok: true }
$body = @{ token = $token } | ConvertTo-Json -Compress
$ok = Invoke-WebRequest -UseBasicParsing -Uri $url -Method POST `
-ContentType "application/json" `
-Body $body -TimeoutSec 5
if ($ok.StatusCode -ne 200) {
Write-Error "/api/pair returned $($ok.StatusCode)"
exit 1
}
Write-Host "/api/pair -> 200 OK: $($ok.Content)"
- name: Run unit tests
shell: pwsh
run: npm test
- name: Stop agent
if: always()
shell: pwsh
run: |
$pidFile = "runtime/agent/agent.pid"
if (Test-Path $pidFile) {
$agentPid = (Get-Content $pidFile -Raw).Trim()
try {
Stop-Process -Id $agentPid -Force -ErrorAction Stop
Write-Host "Stopped agent PID $agentPid"
} catch {
Write-Host "Agent PID $agentPid already gone"
}
}
- name: Upload agent log
if: always()
uses: actions/upload-artifact@v4
with:
name: agent-log
path: |
runtime/agent/agent.log
runtime/agent/agent.port
runtime/agent/agent.pid
if-no-files-found: warn