Stale session JSON survives MCP-shim restart, silently breaks ex (PUB port mismatch) #35
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
kahliburke/Kaimon.jl#35
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
When the Julia gate process is replaced (e.g. Slurm job restart that rebinds
the same REQ port), kaimon resurrects the previous session from
~/.cache/kaimon/sock/.json instead of re-handshaking. REQ-path tools
(ping, connect_tcp, cancel_eval) keep working because the new gate happens to
bind the same REQ port. PUB-path tools (ex) hang forever because the cached
stream_endpoint points at the dead gate's PUB port, which the new gate did
not reuse.
Repro
kaimon-julia-slurm(Slurm: srun --pty julia ... -e 'Kaimon.Gate.serve(mode=:tcp, port=9876)').
Connect via MCP. Confirm
exworks.kaimon-julia-slurmcommand -- new Julia process, same REQ port 9876.pingshows the session as connected.-> hangs >30s, gets promoted to a background job, never completes.
Observed
pingreports session tcp-127.0.0.1-9876 asconnected, free, with PIDmatching the PREVIOUS gate.
~/.cache/kaimon/sock/tcp-127.0.0.1-9876.json still contains the dead
gate's pid and stream_endpoint. Example:
{
"pid": 464140,
"stream_endpoint": "tcp://127.0.0.1:44161",
"last_pong": "2026-04-28T15:26:13.583",
...
}
last_pong keeps updating (REQ port works), so kaimon's liveness check
considers the session healthy.
ss -ltnpshows 9876 bound by the new Julia, but 44161 is NOT listeninganywhere on the host --
nc -z 127.0.0.1 44161-> Connection refused.The new gate picked a different PUB port and never advertised it because
kaimon never re-handshook.
Workaround
After this, kaimon re-handshakes against the live gate, gets the correct
stream_endpoint, and
exreturns immediately.Suggested fix (one or more)
On startup / reconnect, always re-handshake. Treat the on-disk JSON as
a hint (project name, last-known endpoint), not as a source of truth
for pid / stream_endpoint. Rebuild from the live pong response.
Include a gate-instance nonce in the handshake (UUID generated when
Gate.serve starts). Persist it in the session file. If a pong returns
a different nonce than the cached one, invalidate the file and
re-handshake. This survives REQ-port reuse, which PID alone does not
(and PID is also unreliable under Slurm cgroup namespacing -- host PID
and in-cgroup PID differ).
Sanity-check stream_endpoint reachability before declaring the session
"connected". A simple non-blocking connect on the PUB port at handshake
time would have surfaced this immediately as "stream port unreachable"
instead of an indefinite
exhang.Time out promoted background
exjobs when no PUB messages have everbeen received on the session, with an error pointing at stream_endpoint
mismatch -- easier to diagnose than a silent hang.
Environment
julia -m Kaimon, server uptime 16h,MCP listening on 127.0.0.1:2828
julia -t 96 --project=... -i -e 'Kaimon.Gate.serve(mode=:tcp, host="127.0.0.1", port=9876)'under
srun --ptyon a 224-thread Slurm node(kaimon-features) had stale jsons.
still be stale.
What I end up doing each restart: ss -tlnp | grep 987[0-9] (find which port a julia PID is listening on), then connect_tcp(host=127.0.0.1, port=) to manually register, then
ex(ses="tcp-127.0.0.1-", ...). It works reliably but is more steps than it should take.
Fixed in 2.0 — stale session metadata no longer resurrects a dead gate PUB endpoint; the SUB socket is replaced when a gate restarts on a new PUB port, with test coverage. Shipping in 2.0.