chat: strip RFC 5322 display name when matching reply recipients#179
Conversation
Caught while playing Zork over primitive chat as the real-user smoke test. The server returns the from-header form (Zork <zork@...>) in EmailDetail.from_email, but the CLI was doing a literal string compare against the bare recipient the user typed (zork@...). Every reply from a sender that sets a display name (i.e. basically every real service) hit 'Inbound email X does not match recipient Y' on the next primitive chat reply. normalizeEmailAddress now strips the angle-bracket wrapper before lowercasing. Bare addresses pass through unchanged, so existing callers (assertParentMatchesRecipient, findLatestInboundFromRecipient) get the same key on either input shape. Tests cover the bug path plus the round-trip property (wrapped and bare normalize to the same key). 517/517 tests pass (was 512; added 5). Note: this also unblocks the canonical CLI follow-up commands the chat reply printer emits, which were suggesting a primitive chat Zork <...> --reply ... shape that the shell can't even parse cleanly because < and > are redirects. With this fix the bare-address form in those suggestions just works.
Greptile SummaryFixes a recipient-matching bug where
Confidence Score: 5/5Safe to merge — the change is narrowly scoped to one helper function and does not alter any call sites or data flows. The regex No files require special attention. Important Files Changed
Reviews (1): Last reviewed commit: "Strip RFC 5322 display name when matchin..." | Re-trigger Greptile |
Summary
Caught while playing Zork over
primitive chatas the real-user smoke test of the agent-to-agent loop. The server returns the from-header form (Zork <zork@...>) inEmailDetail.from_email, but the CLI was doing a literal string compare against the bare recipient the user typed (zork@...). Every reply from a sender that sets a display name hit:Two identical addresses, rejected as different. That's basically every real service, since most set a display name. Blocks the natural
primitive chat replyflow end-to-end.Fix
normalizeEmailAddressincommands/chat.tsnow strips the angle-bracket wrapper before lowercasing:Bare addresses pass through unchanged, so existing callers (
assertParentMatchesRecipient,findLatestInboundFromRecipient) keep working on the bare-address path AND start working on the display-name path. Exported so it can be unit-tested directly.Bonus side effect
The chat reply printer emits canonical follow-up commands like:
<and>are shell redirects, so even quoting that correctly is annoying. With this fix, agents can use the bare-address form (which is what they have inargs.recipientanyway) and the reply context loader matches it cleanly.Test plan
pnpm lintcleanpnpm typecheckcleanpnpm test517/517 (added 5 cases onnormalizeEmailAddress: bare round-trip, display-name strip, quoted display name, bare angle-bracket wrapper, and the round-trip identity that's the whole point of the fix)primitive chat zork@play.primitive-staging-1.com "open mailbox"thenprimitive chat reply "go north") and confirm the reply context loads cleanly.Upstream note
There's also a server-side oddity worth a separate look:
from_emailandfrom_headerboth carry the display-name form on this email, whilesenderis bare. Naming-wisefrom_emailreads like "bare address" but is actually "from-header form". This CLI fix papers over it; the cleaner long-term fix is makingfrom_emailactually carry the bare address. Not in scope for this PR.Bundle in next release?
Small change, easy to roll into the next coordinated release. Could go in a 0.38.1 patch or wait for 0.39.0.