fix(ci): stop each HTTP end-to-end app fully and start the next from an empty dev cache

Merged
WA
12d ago
+193-70

Summary

The "End-to-end over real HTTP" job's stop-after step (test-workflow-stop-after-e2e.ts) was flaky. This was test infrastructure, not a product bug: no failed run ever reached the executor.

Root cause. All the apps in the job share apps/sim/.next. The stop-after app restored the Turbopack dev cache (.next/dev) that the job's previous app, the SCIM/version-compare one, left behind. That cache was built with different NEXT_PUBLIC_* values by a server that next dev SIGKILLs 100ms after SIGTERM (NEXT_EXIT_TIMEOUT_MS defaults to 100). The step also started while the previous app could still be writing that directory: kill plus wait on next dev returns before its server process tree is gone. An earlier revision of this PR hit that directly. Its rm -rf .next/dev failed with Directory not empty 1s after the previous step ended.

I classified every failed run of this step across 175 CI runs from Oct 1 to Oct 6. 12 were this flake, and all 12 came from the restored cache:

SymptomRunsWhat the server log shows
Local workflow app exited during startup6Turbopack panic inner_of_upper_lost_follower ... aggregation_update.rs
The socket connection was closed unexpectedly4the same panic while compiling /api/v2/workflows/[workflowId]/execute
The operation timed out. after 5 min2○ Compiling /api/v2/workflows/[workflowId]/execute ... that never finishes, with JS heap flat

The other 2 failed runs had real branch errors. In the timed-out runs the cleanup check found no open execution log, so the first request never reached the handler. That rules out a stop-after run that never terminates. Healthy runs of this step took 35–99s (p50 47s).

Changes

  • CI (test-build.yml), for all three app steps:
    • Each app starts in its own session (setsid).
    • The new .github/scripts/stop-session.sh sends SIGTERM to the whole session and returns only once none of it is left running. It escalates to SIGKILL after 10s and fails the step if anything survives that. It logs any process that outlived next dev.
    • Each app then starts from an empty dev cache (rm -rf .next/dev).
    • A failing step prints the server log tail, not only on a startup failure.
  • Stop-after suite:
    • A named first check compiles the execute route with a refused request under its own 300s budget.
    • Every later request and CLI run is bounded at 60s.
    • A timed-out or dropped request names the route and the elapsed time. It lands in the report with status: null, so a status is recorded only for a fully read response.
    • A CLI timeout is reported as a timeout, and an unexpected CLI failure reports its stderr.
  • Desktop inbox suite (#8650): a cold cache exposed that its 10s "all four calls" deadline also covered next dev compiling the claim, lease and complete routes. One CI attempt failed that way. A new check now compiles those three routes with refused bodies before the timed checks.

Evidence

  • Local: a CI-faithful harness on the Mac (5 iterations) and a targeted SIGTERM-then-restore loop (5 cycles) both passed. The panic didn't reproduce on darwin-arm64.
  • Linux EC2 (8 cores via taskset), unchanged code: 8 of 8 iterations passed. On a quiet box the flake doesn't reproduce, and no process outlived next dev there.
  • Linux EC2, this PR: 8 of 8 two-step iterations and 6 of 6 three-step iterations passed. A cold cache moves the stop-after step from ~58s to ~105s.
  • CI reruns of the http-e2e job on this PR: see the comment thread.
  • Gate (EC2): lint, typecheck, audits, migrations, docs, blocks and test all pass. In test, 3 unrelated files failed under load and passed when rerun alone.