The Session JSONL is Haunted
It started with silence.
I run a four-agent AI stack — my Chief of Staff, basically — over WhatsApp. George handles scheduling, social media, ops, and a dozen other things. Most mornings I check in and the system has already handled whatever came in overnight.
One morning in May, I noticed nothing had gone out for hours. Not an error. Not a crash. Just silence.
I opened the gateway logs and saw something strange: the agent had produced replies. Long, well-formed replies. The transcript files had fresh assistant entries with a clean stop signal — stopReason: stop, which is what you see when everything worked. But nothing had reached WhatsApp. Zero dispatch logs. No Sending message. No auto-reply confirmation.
My agent was writing letters and putting them in a drawer instead of mailing them.

—
The instinct: restart the gateway.
That’s what you’d do with any server. The process gets into a bad state, you restart it, the in-memory state clears, problem solved. I’ve restarted the gateway for a dozen smaller issues and it always worked.
Not this time.
After the restart, the same lane was still stuck. The agent would receive a message, generate a perfectly good reply, and then… nothing. The drawer again.
This is the part that takes a minute to internalize: a gateway restart rebuilds sessions from disk. Which means if the problem is on disk, the restart brings the problem back with it.
The session wasn’t stuck in memory. It was haunted on disk.
—
What a haunted session looks like on disk (George co-narrating here)
Every active session in OpenClaw lives in a JSONL transcript file. When the gateway restarts, it reads these files and reconstructs session state. If a session ends mid-write — say, because an API timeout fires during a long response — the JSONL can land in an inconsistent state: the last assistant turn is complete, but the dispatch metadata that tells the gateway “this has been sent” never got appended.
From the gateway’s perspective on restart, the session looks like it has an un-dispatched reply. But the logic that decides how to dispatch it — which channel, which message ID to reply to — is also stale or missing. So the agent dutifully generates a new reply, marks it complete… and then hits the same broken dispatch path.
It’s not an infinite loop. It’s more like a door that looks closed but the latch doesn’t catch. Every time someone tries to open it, it swings and snaps back.
The fix — the real one — requires surgery on disk:
1. Stop the gateway
2. Delete the three files associated with the stuck session: the JSONL transcript, the trajectory file, and the trajectory path pointer
3. Remove the session’s entry from sessions.json
4. Start the gateway
5. The next inbound message rebuilds the session clean from scratch
It’s a five-minute operation once you know what to look for. The hard part is knowing what you’re looking for.
—
Why this matters if you’re building anything like this
Most people who write about running Claude Code or multi-agent systems in production describe the happy path. The gateway starts, the agents respond, everyone goes home. Debugging guides assume the problem is a crashed process or a bad API key.
The haunted-session failure mode is different. The process is healthy. The credentials are valid. The model is responding. The problem is in the state layer that sits between the model and the outside world — and that layer is on disk, so it outlives every operation you’d naturally reach for first.
I now monitor for the signature: sessions that have recent assistant turns in their transcript but no corresponding dispatch event in the gateway log. If a session generates without dispatching twice in a row, it gets flagged for manual review. That’s not an automated fix — it’s a dead-man’s switch that tells me something needs human eyes.
The bigger lesson is about durability models. When you build a system where state is written to disk before being dispatched to the outside world, you get crash recovery for free — but you also get a new failure mode where the saved state and the actual world diverge. That divergence can be invisible to every health check you’d normally run.
My agent wasn’t broken. It was just writing letters and putting them in a drawer.
The drawer, it turns out, survives reboots.
—
I’m packaging the skills, configs, and gotchas from these posts into a Build Your Own Chief starter kit. Join the waitlist to get it first.