Repository navigation
test(repo): fix e2e port races and name storage crash-test stalls - #7504
Conversation
…e clock The stalled-list test advanced the TestClock in fixed steps right after forking the command. When the command's setup took longer than those steps, the request timeout was scheduled after the clock had already moved, and the test hung until the real test timeout. The mock now signals each stalled attempt and the test advances the clock only after that attempt is in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MBEk1JxfLqmcaNWvN6MRyG
The e2e helper picked stack ports by binding port 0 and releasing it. Those ports come from the kernel's ephemeral range, so an outbound connection on the runner could take one before Docker published it, failing the stack start with `address already in use`. Ports now come from a fixed range below the Linux and macOS ephemeral ranges, probed on every interface as Docker binds them, and still partitioned per e2e worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MBEk1JxfLqmcaNWvN6MRyG
The kill-between-staging-and-publishing test hit the 20s test timeout without saying which step hung; only the writer's readiness had its own deadline. Each remaining phase (helper image, prepare, PG_VERSION seed, killed writer exit and pipe close, markInitialized retry, and both cleanups) now fails with a tagged error naming the phase well before the test timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MBEk1JxfLqmcaNWvN6MRyG
|
/ai-review Generated by Claude Code |
CLI benchmark (Lightweight Commands): no changes foundCommit
API calls: unchanged for all 7 commands. Binary size: 134.6 MB, unchanged. Details: timing ranges, API calls per command, slowest phasesTiming ranges
API calls per command
Slowest phasesNo command moved beyond noise, so there are no phase deltas. Comment |
There was a problem hiding this comment.
Superseded by a newer AI review
🤖 AI Review
Reviewed both independent reviews and all three changed files. Claude's finding is refuted: duplicate port draws are possible, but current callers of that helper start only PostgreSQL; full-stack tests use the allocator that deduplicates. No confirmed defects found. Tests were not run because dependencies are absent.
Findings
No issues found.
Refuted findings (kept for transparency, not posted as review comments)
apps/cli/tests/helpers/cli.ts:332(test-flakiness): Independent random draws without deduplication in makeTempStackProjectEffect can assign the same port to multiple services, producing Docker address-in-use failures with roughly 5% probability per project when four workers run.
Refuted: The duplicate-draw observation is correct, but the claimed test failures do not follow from current callers. The only consumers are db/start/start.e2e.test.ts and gen/types/types.e2e.test.ts. The former invokes db start at lines 24-28, whose implementation starts only the database using values.dbPort (command-internal/db-bootstrap/start-local-database.ts:247-270). Local typegen overwrites the generated config and starts bare PostgreSQL using only project.ports.dbPort (types.e2e.test.ts:333-335), publishing only that port at lines 226-234; remote typegen starts no local services. Full-stack tests instead call overrideStackPorts, which explicitly rejects duplicate allocations (tests/helpers/cli.ts:354-368). Thus approximately 5% describes any duplicate among 18 draws, including unused values, rather than an address-in-use failure probability.
Stats
Claude findings: 1 · Codex findings: 0 · Confirmed: 0 · Refuted: 1 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: auto · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
Each pick drew a fresh random start in the fixed range, so the many ports a temp stack project allocates at once could repeat within a worker's slot. The picker now walks the range from one random start per worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MBEk1JxfLqmcaNWvN6MRyG
Flaky test check: ✅ no flaky testsCommit Each execution is a separate Vitest process; |
There was a problem hiding this comment.
Superseded by a newer AI review
🤖 AI Review
Verified all three findings. Refuted the claimed port-collision flake for current callers. Confirmed overly restrictive phase deadlines and, with corrected reasoning, cleanup paths that can outlast their deadlines.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | packages/stack/src/storage/DockerDatabaseStorage.integration.test.ts:1153 |
test-flakiness |
claude | The diagnostic phase deadlines introduce failures for successful Docker operations that exceed five seconds but would fit within the overall test budget. Cleanup deadline failures also become defects instead of being ignored. |
| 🟡 MINOR | packages/stack/src/storage/DockerDatabaseStorage.integration.test.ts:1155 |
test-reliability |
codex | The destroyData cleanup deadline can still wait indefinitely without reporting its phase because the storage implementation explicitly masks interruption during helper removal. |
Refuted findings (kept for transparency, not posted as review comments)
apps/cli/tests/helpers/cli.ts:336(test-flakiness): Independent random port picks without deduplication cause approximately 4.7% of stack setups to receive duplicate service ports and fail when Docker publishes them.
Refuted: Duplicate picks are possible, but the claimed failure does not follow for current callers. The db-start test starts only PostgreSQL; db-bootstrap/postgres.service.ts:320 publishes only db.port. The typegen test rewrites the configuration and starts PostgreSQL using only project.ports.dbPort at types.e2e.test.ts:333-335; its other caller exercises a remote project. Full-stack configurations use overrideStackPortsEffect, which deduplicates assignments at cli.ts:354-368. The birthday calculation therefore does not establish the claimed setup failure rate.
Stats
Claude findings: 2 · Codex findings: 1 · Confirmed: 2 · Refuted: 1 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
…es-2026-11-05 # Conflicts: # apps/cli/src/commands/sso/list/list.integration.test.ts
The fixed 20000-32767 range was the stack's public auto-port range, so a concurrent stack-mode e2e could bind a port another worker had probed but not yet published. Ports now come from 1024-9999, below both stack ranges and the OS ephemeral ranges, and are also probed on loopback because macOS lets a wildcard bind succeed over a loopback-only listener.
|
/ai-review |
There was a problem hiding this comment.
🤖 AI Review
Verified all five findings against the code: four confirmed and one refuted. Confirmed concerns cover duplicate concurrent port allocations, missing IPv6 probes, cleanup deadlines introducing failures, and phase budgets exceeding the test timeout. The claim that cleanup deadlines inherit finalizer uninterruptibility is refuted by the bundled Effect implementation and a controlled reproduction. No released CLI behavior changes.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | packages/stack/src/storage/DockerDatabaseStorage.integration.test.ts:1155 |
test-reliability |
claude | The new five-second cleanup deadlines can turn otherwise passing tests into failures when best-effort Docker cleanup is merely slow. Effect.ignore precedes the deadline, so it does not swallow PhaseStallError, which Effect.orDie converts into a defect. |
| 🟡 MINOR | packages/stack/src/storage/DockerDatabaseStorage.integration.test.ts:1129 |
test-reliability |
claude | The sequential phase budgets exceed the unchanged 20-second test timeout, so slow earlier phases can leave a later phase hitting the generic test timeout before its named deadline. The individual deadlines also reject some executions that previously completed within the overall timeout. |
| 🟡 MINOR | apps/cli/tests/helpers/cli.ts:348 |
test-reliability |
claude | The allocator checks only IPv4, so an IPv6-only listener can occupy a selected port and cause Docker's subsequent publication to fail on hosts where Docker also publishes over IPv6. |
| 🟡 MINOR | apps/cli/tests/helpers/cli.ts:343 |
concurrency |
codex | Concurrent allocations can return the same port because retries ignore advances made to the shared cursor. |
Refuted findings (kept for transparency, not posted as review comments)
packages/stack/src/storage/DockerDatabaseStorage.integration.test.ts:1155(error-handling): The cleanup deadlines cannot reliably terminate stalled cleanup because they inherit the finalizer's uninterruptible status.
Refuted: The unchanged Effect implementation bundled in packages/stack/src/functions/generated/serve-main-bundle.ts:3 implements timeoutOrElse through a race whose child fibers start interruptible rather than inheriting the parent's status. A reproduction using that runtime and the same finalizer pipeline interrupted a 200ms cleanup and returned the deadline defect after 22ms. Explicitly uninterruptible operations such as DockerDatabaseStorage.ts:753-774 can still delay cancellation, but finalizer inheritance does not disable these deadlines.
Stats
Claude findings: 3 · Codex findings: 2 · Confirmed: 4 · Refuted: 1 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
TL;DR
Fixes an e2e port race and adds named per-phase deadlines to a test whose stall cause is still unknown. The
sso listflake originally in this PR is already fixed ondevelopby #7502.statuse2e › reports a running local stack… failed with Kong's host portaddress already in use. The e2e helper picked ports by binding port 0 and releasing it, so they came from the kernel's ephemeral range, where an outbound connection on the runner could take one before Docker published it. Ports now come from 1024–9999, below both the stack's own allocation ranges (10000 and up, seepackages/stack/src/Ports.ts) and the OS ephemeral ranges, so neither a concurrent stack-mode e2e nor an outbound connection can claim a probed port. Each port is probed on0.0.0.0, as Docker binds it, and on127.0.0.1, because macOS lets a wildcard bind succeed over a loopback-only listener. Ports stay partitioned per e2e worker.DockerDatabaseStorage› keeps the prior marker decodable when a real kill lands… hit the 20s test timeout and the failure didn't say which step hung. Each remaining phase (helper image,prepare, PG_VERSION seed, killed writer exit and pipe close,markInitializedretry, both cleanups) now fails with aPhaseStallErrornaming the phase, well before the test timeout. This is diagnostic-only; the next failure will name the phase to fix.ref
spotted in: