fix(ci): stop each HTTP end-to-end app fully and start the next from an empty dev cache
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:
| Symptom | Runs | What the server log shows |
|---|---|---|
Local workflow app exited during startup | 6 | Turbopack panic inner_of_upper_lost_follower ... aggregation_update.rs |
The socket connection was closed unexpectedly | 4 | the same panic while compiling /api/v2/workflows/[workflowId]/execute |
The operation timed out. after 5 min | 2 | ○ 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.shsends 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 outlivednext 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.
- Each app starts in its own session (
- 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 devcompiling 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 outlivednext devthere. - 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,blocksandtestall pass. Intest, 3 unrelated files failed under load and passed when rerun alone.