Skip to content

mail post refusals are broadcast to every client via announceError #305

Description

@scgopi

Found by the MailPoster end-to-end loop against the shipped 0.1.64-beta2 (build 252), and measured: 1 of 1 leaked to a second open connection.

The defect

When mailroomPost refuses a post — over the 1024-byte bound, room off, no loop identity — it answers through announceError, which writes to every connected client. So one loop posting an oversized note makes every other app window and CLI print an error about somebody else's failed post.

On this graph that is 30-plus loops receiving an errorOccurred for a command they did not send.

Why this is the same bug twice

#295 fixed exactly this shape on the read side. refuseLegacyInbox(to:) and MailboxRefusal now route a refusal to the asking connection alone, and GraphStore.swift carries the reasoning:

To the asker alone, never announceError: that writes to every connected client, and one stale CLI's problem is nobody else's error to read.

The post side never got the same treatment. The rule was established and then applied to one path.

Suggested fix

Route mailroomPost refusals the way mailbox refusals now go — to the asking connection, using the connectionID the registry already threads through GraphStore.handle(_:from:) for refuseLegacyInbox. The mechanism exists; it just is not used here.

Worth auditing the other announceError call sites at the same time: any refusal that answers one caller's command belongs to that caller. announceError is right for a condition the whole graph needs to know about, and wrong for "your command was rejected".

Also confirmed while finding it (all correct, no action)

  • The 1024-byte bound is daemon-side, not CLI-only — a raw DaemonCommand.mailroomPost over the socket gets the same refusal.
  • The bound counts UTF-8 bytes: a 1024-byte multibyte body is accepted.
  • 1023 and 1024 accepted and stored intact; 1025 and 1026 refused.
  • Bodies are whitespace-trimmed before storing (by design).

Related: #288, #295

🤖 Generated with Claude Code

https://claude.ai/code/session_01BP43ags4cn8fq2ZZdv85J9

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions