You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A registry directory can be shared by processes that cannot see each other's servers. The easy way to get there is several containers bind-mounting the same home directory — which is how agent harnesses tend to run. The registry is shared; sockets live in each container's own /tmp, and PIDs are per-namespace.
So every liveness test fails on a sibling's record — socket missing, pid absent — and pruning deletes the record of a server that is alive and well.
The owner then cannot find its own server: decompiler list returns nothing while the process is running and serving, and load blocks until --timeout expires waiting for a server that started successfully seconds earlier.
Traced
Instrumenting every registry mutation across two concurrent containers:
On a 40-binary benchmark, 22 of 40 solves hit a phantom Timed out waiting … for server to start — 50 occurrences in a single run — each burning the full timeout (up to 300s) while the server sat healthy. Reproduced directly with two containers sharing one home:
result
shared registry
registered 0 server(s) … 0 visible, 1 process alive
isolated registry
each container: 1 visible, 1 process alive
Change
Records carry the namespace that created them (hostname + pid/mnt namespace links), which makes "not mine" distinguishable from "dead". Foreign records are skipped entirely — never returned as ours, never deleted — so a container is the only writer of its own entries.
Records without the field are treated as local, so existing registries prune exactly as before.
This is defense in depth, not a licence to share: deployments should still give each container its own registry (we set XDG_STATE_HOME per container). It makes the shared case degrade into "I can't see it" instead of "I deleted it".
Tests
Four: foreign records are never returned nor deleted; our own dead records are still pruned; new records carry a namespace; legacy records without one still prune.
A registry directory can be shared by processes that cannot see each
other's servers. The easy way to arrive there is several containers
bind-mounting the same home directory, which is how agent harnesses tend
to run: the registry is shared, but sockets live in each container's own
/tmp and PIDs are per-namespace.
Every liveness test then fails on a sibling's record -- socket missing,
pid absent -- so pruning deletes the record of a server that is alive and
well. The owning container subsequently cannot find its own server:
`decompiler list` returns nothing while the process is running and
serving, and `load` blocks until its --timeout expires waiting for a
server that started successfully seconds earlier.
Traced across two concurrent containers:
container A: REGISTER 6fe01da235 (pid 1301)
container B: PRUNE 6fe01da235 socket_exists=False pid_alive=False
container B: REGISTER 89b45700ed (pid 1358)
container A: PRUNE 89b45700ed socket_exists=False pid_alive=False
On a 40-binary benchmark this cost 22 of 40 solves a phantom
"timed out waiting for server to start", 50 occurrences in one run.
Records now carry the namespace that created them, which makes "not
mine" distinguishable from "dead". Foreign records are skipped entirely:
never returned as ours, never deleted. Records without the field are
treated as local, so existing registries keep working exactly as before.
Deployments should still give each container its own registry; this makes
the shared case degrade into "cannot see it" rather than "deleted it".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both existing checks are proxies for the question actually being asked.
A socket file survives an unclean death, so its presence proves only that
a server once bound there. A PID can be recycled by an unrelated process.
Either can report "alive" about a server that is gone -- and, across a
shared registry, "dead" about one that is fine.
ida-pro-mcp settles the same question by connecting to the endpoint
(`probe_instance`), for the same reasons; its comment cites PID reuse and
"process alive but the server crashed". Do likewise: after the two cheap
checks pass, connect to the socket.
Verified against a real backend: a live server is still reported and
usable; after SIGKILL the socket file remains on disk and the server is
correctly reported gone, where the file check alone would have said it
was alive.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
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.
The bug
A registry directory can be shared by processes that cannot see each other's servers. The easy way to get there is several containers bind-mounting the same home directory — which is how agent harnesses tend to run. The registry is shared; sockets live in each container's own
/tmp, and PIDs are per-namespace.So every liveness test fails on a sibling's record — socket missing, pid absent — and pruning deletes the record of a server that is alive and well.
The owner then cannot find its own server:
decompiler listreturns nothing while the process is running and serving, andloadblocks until--timeoutexpires waiting for a server that started successfully seconds earlier.Traced
Instrumenting every registry mutation across two concurrent containers:
They annihilate each other on every load.
Cost
On a 40-binary benchmark, 22 of 40 solves hit a phantom
Timed out waiting … for server to start— 50 occurrences in a single run — each burning the full timeout (up to 300s) while the server sat healthy. Reproduced directly with two containers sharing one home:registered 0 server(s)…0 visible, 1 process alive1 visible, 1 process aliveChange
Records carry the namespace that created them (hostname + pid/mnt namespace links), which makes "not mine" distinguishable from "dead". Foreign records are skipped entirely — never returned as ours, never deleted — so a container is the only writer of its own entries.
Records without the field are treated as local, so existing registries prune exactly as before.
This is defense in depth, not a licence to share: deployments should still give each container its own registry (we set
XDG_STATE_HOMEper container). It makes the shared case degrade into "I can't see it" instead of "I deleted it".Tests
Four: foreign records are never returned nor deleted; our own dead records are still pruned; new records carry a namespace; legacy records without one still prune.
🤖 Generated with Claude Code