[BUG] Team-inbox `read` flag is set when message is queued, not when agent has actually seen it

Resolved 💬 2 comments Opened May 11, 2026 by Butanium Closed May 11, 2026

Summary

When a teammate sends me a message while I'm mid-turn, the harness writes the entry to ~/.claude/teams/<team>/inboxes/<recipient>.json and immediately flips "read": true on it — before the message has actually been injected into my next turn. The flip happens as soon as the harness queues the message for next-idle delivery, not when the agent has been notified.

This makes the field name a lie: read does not mean "the recipient has seen this," it means "the harness has noticed this and will deliver it at the next idle." From the outside (file inspection, MCP servers, tooling), there is no way to tell those two states apart.

Repro

  1. Spawn a team and a teammate:

``python
TeamCreate(team_name="inbox-bug-test")
Agent(team_name="inbox-bug-test", name="messenger",
prompt="Call SendMessage(to='team-lead', message='PING'). Stop.",
run_in_background=True)
``

  1. While still in your turn (don't idle), read the inbox file:

``
$ cat ~/.claude/teams/inbox-bug-test/inboxes/team-lead.json
[
{
"from": "messenger",
"text": "PING",
"timestamp": "...",
"read": true # <-- already true, before any delivery
},
{
"from": "messenger",
"text": "{\"type\":\"idle_notification\",...}",
"read": true # <-- same
}
]
``

  1. End the turn. The harness still injects both entries into the next turn as <teammate-message> blocks — i.e. they were genuinely undelivered at the moment read: true was written.

So the file says read: true during a window in which the agent has not been informed, and the harness itself disagrees with the flag (it delivers the messages anyway).

Why this is a problem

  • The field name is misleading. Any reasonable reader of the file (human or tool) interprets "read": false as "this is what hasn't been shown to the agent yet." That interpretation is wrong.
  • No external observer can distinguish queued-but-undelivered from delivered-and-consumed. Both look like read: true in the file. The interesting state — "the agent hasn't seen this yet, but a reply is sitting in the inbox" — is unrepresented.
  • Mid-turn introspection is impossible. This breaks the natural use case of "before I send another message to this teammate, did they already reply?" — there's no signal in the file (and no other surface) to answer that question without ending the turn.
  • It silently breaks downstream tooling. I have a small MCP (team-inbox) whose fetch_unread tool filters on read == false. It returns "No unread messages." precisely in the window when the answer is "yes, there's a pending reply." The tool isn't wrong about what it does; the underlying field is wrong about what it means.

Suggested fix

Flip read: true only after the message has actually been injected into the recipient's turn — i.e. at delivery time, not at queue time.

If there's a reason to record the queue event in the file (auditing, debugging, race-recovery on harness crash), add a separate field for it — e.g. queued_at or acked_by_harness — and leave read to mean what its name promises.

Workaround (current state)

Inside the MCP I'm having to filter on include_read=True to see anything useful, which defeats the point of the unread/read distinction entirely. Any other tool inspecting these files will hit the same dead end.

Environment

  • Linux, Claude Code CLI
  • CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
  • Confirmed on session dated 2026-05-11

---

🤖 Generated with Claude Code

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗