fix(serve): survive dual-stack port collisions at startup - #1406
Merged
Conversation
An unrelated IPv4 listener can make daemon readiness target the wrong process while AgentsView serves on IPv6. Record the chosen collision and identity-probe behavior before changing the shared serve lifecycle.
On macOS a wildcard listen can bind IPv6 while an unrelated process still owns the same port on IPv4. Port selection accepted that port, readiness passed on a bare TCP dial that reached the IPv4 process, and an attached daemon restart then waited forever for a runtime record the identity probes kept rejecting. Port selection now probes the IPv4 and IPv6 wildcard addresses separately and moves to the next port when either family is occupied. A family that cannot bind at all stays out of the check, so IPv4-only hosts keep their behavior. Backend readiness now requires the authenticated daemon ping to answer from this process instead of accepting any TCP connection, so no foreign listener can make startup publish a healthy runtime. Managed Caddy keeps its generic TCP check because Caddy does not speak the daemon protocol. The design spec gains the two decisions this uncovered: bind-family classification for IPv6-disabled hosts and the process ID match in the readiness probe.
Identity-aware readiness originally assumed that every daemon ping lived at the root URL. Read-only PG and DuckDB servers can mount the full application below a base path, so that assumption made valid deployments time out and stop after opening their listener. Use the server's mounted ping path for readiness. This keeps the stronger process identity check without regressing base-path deployments.
roborev: Combined Review (
|
A split IPv4/IPv6 collision means startup does not yet know which process owns the advertised loopback endpoint. Sending the configured bearer token during readiness can therefore disclose a reusable credential to an unrelated listener. Use a temporary server-held challenge proof and reject redirects so readiness authenticates the first listener without exposing the bearer token. Fail an exhausted port search and validate ephemeral wildcard assignments so collision fallbacks never reuse a known occupied port.
roborev: Combined Review (
|
The implementation and its regression coverage now carry the startup behavior. Keep the branch focused on the shipped change instead of retaining its planning artifact.
roborev: Combined Review (
|
wesm
added a commit
to salmonumbrella/agentsview
that referenced
this pull request
Aug 16, 2026
…dy-core * origin/main: (25 commits) ci: retry failed Docker image builds (kenn-io#1437) Speed up HTTP sync by processing only changed sessions (kenn-io#1414) chore: remove dead code and unused frontend exports (kenn-io#1434) fix(parser): populate Kimi session cwd (kenn-io#1427) feat(frontend): add raw and formatted tool output display (kenn-io#1424) feat(insights): support OpenAI-compatible endpoints (kenn-io#1430) fix(parser): support legacy Zed thread schemas (kenn-io#1429) perf(signals): index duplicate prompt comparisons (kenn-io#1425) fix(config): honor explicit empty agent directory arrays (kenn-io#1423) feat(parser): add DeepSeek Harness session support (kenn-io#1402) test(sync): wait for archive audit stall state (kenn-io#1422) chore(deps): update github actions dependencies (kenn-io#1421) Tombstone stored Claude sessions a complete full parse no longer emits (kenn-io#1392) fix(sync): report stalled syncs as unhealthy (kenn-io#1419) Reduce idle CPU and let users turn off unused providers (kenn-io#1374) fix(serve): survive dual-stack port collisions at startup (kenn-io#1406) fix(sync): bound startup reconciliation and expose daemon identity (kenn-io#1413) fix(deps): update module golang.org/x/mod to v0.40.0 [security] (kenn-io#1400) Persist Claude and Codex freshness digests across engine restarts (kenn-io#1393) fix(sync): add lifecycle logging (kenn-io#1399) ...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wildcard startup could select a port already occupied on IPv4 by binding only
its IPv6 side. The daemon then advertised the IPv4 endpoint, where readiness
could contact the unrelated process and leave an attached restart waiting after
the real server had started.
Wildcard port selection now checks supported IPv4 and IPv6 families separately
and uses the existing next-port fallback when either side is occupied.
Ephemeral wildcard assignments receive the same cross-family validation, and
startup returns an error instead of reusing an occupied port when the candidate
range is exhausted.
Backend readiness now uses a temporary server-held challenge proof. It never
sends the configured bearer token to an unverified listener, rejects redirects,
and follows the server's configured base path. The temporary proof is disabled
as soon as startup completes. Managed Caddy retains its generic TCP readiness
check because it does not implement the AgentsView startup proof.
Unsupported address families remain excluded from port selection, preserving
IPv4-only hosts. A process can still race to claim a selected port before bind,
but an unrelated listener cannot forge readiness, receive the reusable token,
or publish as a healthy AgentsView runtime.