fix(browse): port allocator range actually stays below the ephemeral floor; terminal-agent retries a raced bind

RANDOM_PORT_MAX was 60000 while the module header documents 49152-65535 as
the pool to avoid — ~22% of allocations landed back inside it, preserving
the phantom-404 squatting class for both the daemon and the weeks-lived
terminal-agent. The cap is now 49151 and the range test pins the true
property (< 49152) instead of the old <= 60000 tautology.

terminal-agent boot also re-allocates and retries up to 5 times when
Bun.serve throws in the probe-then-bind TOCTOU window — previously a
concurrent bind killed the boot with no retry via main().catch → exit 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 13:28:57 -07:00
co-authored by Claude Fable 5
parent e86dcd6a22
commit c10a9736b8
3 changed files with 30 additions and 10 deletions
@@ -1,6 +1,6 @@
/**
* #2314: the terminal-agent must allocate its port from the SAME fixed
* 10000-60000 scan range the main server uses (port-allocator.ts,
* 10000-49151 scan range the main server uses (port-allocator.ts,
* decision 8) — never `port: 0`. Binding 0 drew from the OS ephemeral range
* (49152-65535 on macOS), where the weeks-lived agent squatted ports that
* short-lived `app.listen(0)` test servers expected to receive, absorbing
@@ -26,8 +26,10 @@ describe('shared port allocator (#2314)', () => {
const port = await findAvailablePort();
expect(port).toBeGreaterThanOrEqual(RANDOM_PORT_MIN);
expect(port).toBeLessThan(RANDOM_PORT_MAX);
// The load-bearing property: below the ephemeral floor (49152).
expect(RANDOM_PORT_MAX).toBeLessThanOrEqual(60000);
// The load-bearing property: the WHOLE range sits below the ephemeral
// floor (49152). The original 60000 cap left ~22% of picks inside the
// pool this allocator exists to avoid.
expect(RANDOM_PORT_MAX).toBeLessThan(49152);
expect(RANDOM_PORT_MIN).toBeGreaterThanOrEqual(1024);
}
});