There is a fleet of Claude Code agents running on a single MacBook in my office — more than a dozen persistent AI daemons, each scoped to a domain of my own work, each listening on its own set of shared channels so the fleet can coordinate like a team. Most of the time it works. This is about the failure mode that doesn’t announce itself: a daemon that is talking — correctly, usefully, on time — into a room that cannot hear a word of it.
Over two days I watched three versions of that same bug. Not one of them threw an error. That’s the whole point.
Speaking, unheard
The first was a project-scoped daemon — one member of the fleet, quietly doing its own job. For a while it had been quietly useless, and nobody could say why. It was awake. It was processing. Read the channel history and its messages were right there on the record, well-timed and sensible. They had been delivered. And still nothing in the fleet ever answered them — because, it turned out, nothing had heard them.
The bug wasn’t on the speaker’s side at all. Each daemon loads a list of which siblings it’s allowed to hear once, at startup, and holds it in memory. Every listener in the fleet was running a stale copy of that list — a cached snapshot from before this daemon existed. So its messages landed in the shared channel, permanently, fetchable all night, and woke no one. It was on the record and off the air at the same time.
The part that stuck with me was the fix. The corrected list was already on disk, fleet-wide — the change had been deployed. But the running processes were still carrying the old snapshot in memory, because the restart we’d been using reloaded the cache instead of re-reading the file. A soft restart kept the ghost. Only a hard stop-and-start, one daemon at a time, forced each listener to actually read the config that had been sitting there, correct, the entire time. The configuration was fixed. The running system just hadn’t noticed.
That is a far more unsettling failure than a dropped message, and it’s worth saying plainly: the configuration you shipped is not necessarily the configuration that’s running.
Ears, but no voice
The next morning handed me the mirror image. We had spun up some newer daemons, and I was testing whether they could hold a conversation in threaded sub-channels. They could read them fine — every message landed as it happened. But when they tried to reply, every attempt bounced back the same way: Unknown Channel.
So these daemons could hear the room perfectly and could not speak into it at all. And the failure was invisible from outside: a daemon that never replies looks exactly like a daemon with nothing to say. The ones that could still post filled the silence; the mute ones just sat there, unable to raise a hand.
Here’s where it got stubborn. We had a control case — one new daemon that could post to threads while its siblings couldn’t. That should have cracked it. Instead it mostly killed theories. The mute daemons weren’t a “new batch” problem: one of them was actually older than the daemon that worked. They weren’t a single-identity problem either: two daemons sharing the exact same identity landed on opposite sides of the bug. Every clean explanation the control case let us form, it also let us disprove. We narrowed the failure to the permission layer and could not, that morning, pin which part of it — and then the test got torn down before we finished. The bug that hides while it’s happening hides while you’re hunting it, too.
The message that was never sent
The third one happened while we were still cleaning up the first two, and it’s the purest version of the whole problem — because this time there was no transport to blame at all.
One daemon was asked to post something to the group. It composed the message, in full, correctly. And then it simply never called the function that sends it. From its own point of view the message was done — written, finished, delivered. When another daemon asked about it, the first one vouched for it — confirmed it had posted, because as far as its own reasoning went, it had. The message existed nowhere but in its head.
It got caught the only way a thing like that can: someone pulled the actual channel history from the API and found nothing there. The gap between “I wrote it” and “I sent it” had swallowed the message whole, and the sender was the last one who would ever notice — because the sender was certain.
If the first two failures were a message that left and wasn’t heard, this one is a message that was never emitted by a speaker convinced it had spoken. In a system where components report their own success, that’s the failure that should scare you most: the only witness to the problem is the one component that can’t see it.
Why this is the bug I worry about
Put these three failures side by side and you get the thing I actually want to flag, because it is not really about chat channels.
A component that crashes is easy. It’s loud, it leaves a stack trace, something goes red. The dangerous failure is the one where a component believes it succeeded and the rest of the system believes it received nothing — and from where each of them stands, neither is wrong. Speaker thinks it spoke. Room heard silence. No error ever crosses the gap between them, because the gap is the missing error.
If that sounds abstract, it is the single most common way security monitoring fails, and I’ve watched it burn teams that were doing everything else right:
- The detection rule that fires perfectly — into an alert channel nobody has watched since the analyst who owned it left.
- The log source that stops shipping, so the dashboard goes quiet, and quiet reads as calm instead of blind.
- The pager route pointed at a queue that no longer pages anyone.
In every one of those the system is technically working. The rule matched. The pipeline returned success. The alert “sent.” And the whole time, something is talking to a room that can’t hear it — and the first anyone learns of it is the incident review, where you find the alert that did its job hours before a human ever saw it.
What I do about it now
The lesson my fleet keeps re-teaching me is that emitting is not delivering, and you have to test the difference on purpose. A few habits fall straight out of it:
- Verify the channel end-to-end, not just the send. A green “message sent” or “rule enabled” is a claim about your side of the wall. Push a real signal all the way through and confirm a human — or the next system — actually received it. When we finally fixed the deaf listeners, the proof wasn’t the corrected config file — it was bouncing one daemon as a canary, having the silenced one send it a live ping, and watching it answer in real time. The round-trip is the evidence; the config diff is only a promise.
- Check what’s running, not what’s committed. A fix on disk and a fix in the live process are different facts. The most dangerous config is the one you already corrected and never reloaded — green in the repo, wrong in memory.
- Alert on silence. If something that should speak every hour goes quiet, that absence has to page you. Missing signal is signal. A channel that’s gone suspiciously peaceful is the thing to distrust first.
- Keep a control case. The fastest way to find why one node can’t talk is a near-identical node that can. Half of debugging a silent failure is proving the silence isn’t just “nothing to say.”
None of this is exotic. It’s the unglamorous plumbing between “the system ran” and “the system was heard” — and in a fleet of agents, same as in a security program, that gap is exactly where the quiet, expensive failures live.
The silenced daemon is back in the conversation. The mute ones are getting their voices sorted. And I have a new first question for anything in the fleet that looks like it’s behaving: not is it running? — but is anyone actually hearing it?