Skip to content

fix(proxy): bounded EADDRINUSE retry for the mitmdump listen port - #36

Merged
Juliusolsson05 merged 1 commit into
mainfrom
fix/mitmdump-port-eaddrinuse-retry
Jul 21, 2026
Merged

Juliusolsson05 merged 1 commit into
mainfrom
fix/mitmdump-port-eaddrinuse-retry

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Fixes the getFreePort → --listen-port TOCTOU (Juliusolsson05/agent-code#495 finding A7): on a busy machine another process can take the port in the gap between the throwaway bind-then-close and mitmdump's own bind; mitmdump exits address already in use before writing its CA and the whole Claude session fails to start, with no retry.

start() now retries up to 3 attempts with a fresh port each time, mutating info.proxyPort/info.proxyUrl per attempt.

Design notes:

  • Retry over --listen-port 0: the actually-bound port would have to be scraped from mitmdump's human-readable stderr banner (version-fragile), and info.proxyUrl consumers would need a post-start handshake. With retry, info is final once start() resolves — the existing contract (agent-code's claudeSession reads info.proxyUrl only after awaiting start()).
  • Only the pre-CA port-collision death retries (stable address already in use/EADDRINUSE substrings from the stderr tail waitForCa embeds); every other failure rethrows immediately and still writes startup-error.json.
  • Manual cleanup between attempts, not stop() — stop()'s child.once('exit') handshake never fires for an already-exited child and would hang.

Verified by type-checking through the agent-code superproject gate (tsc -b tsconfig.node.json with this file patched into packages/claude-code-headless), since the package has no standalone @types/node devDep.

The agent-code submodule pointer bump follows in Juliusolsson05/agent-code once this lands (noted in its Part 4 PR).

🤖 Generated with Claude Code

The port is chosen by bind-then-close of a throwaway net server
(getFreePort) and handed to mitmdump via --listen-port moments later — a
TOCTOU gap in which any other process can grab the port. When that
happens mitmdump exits 'Address already in use' before writing its CA,
waitForCa throws, and the whole provider session (the spine of every
Claude run) fails to start with no retry (agent-code#495 A7).

start() now retries up to 3 attempts, re-running getFreePort and
rewriting info.proxyPort/proxyUrl per attempt. Retry-with-fresh-port was
chosen over --listen-port 0 because the bound port would have to be
scraped from mitmdump's human-readable stderr banner (version-fragile)
and info.proxyUrl consumers would need a post-start handshake; with the
retry, info is final once start() resolves, which is the existing
contract (claudeSession reads info.proxyUrl only after awaiting start()).

Only the pre-CA port-collision death retries (matched on the stable
'address already in use'/EADDRINUSE substrings from the stderr tail that
waitForCa embeds in its error). Everything else rethrows immediately.
Cleanup between attempts is manual (child=null, reset exit fields, clear
stderr tail) rather than stop() — stop()'s child.once('exit') handshake
never fires for an already-exited process and would hang forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 force-pushed the fix/mitmdump-port-eaddrinuse-retry branch from 280522e to 8c3f495 Compare July 21, 2026 14:27
@Juliusolsson05
Juliusolsson05 merged commit 444430b into main Jul 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant