Skip to content

chat: strip RFC 5322 display name when matching reply recipients#179

Merged
etbyrd merged 1 commit into
mainfrom
chat-reply-display-name-match
Jun 1, 2026
Merged

chat: strip RFC 5322 display name when matching reply recipients#179
etbyrd merged 1 commit into
mainfrom
chat-reply-display-name-match

Conversation

@etbyrd
Copy link
Copy Markdown
Member

@etbyrd etbyrd commented Jun 1, 2026

Summary

Caught while playing Zork over primitive chat as the real-user smoke test of the agent-to-agent loop. 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 hit:

Inbound email <id> does not match recipient <recipient>.
Error: Inbound email <id> is from Zork <zork@play.primitive-staging-1.com>,
not zork@play.primitive-staging-1.com. Use ...

Two identical addresses, rejected as different. That's basically every real service, since most set a display name. Blocks the natural primitive chat reply flow end-to-end.

Fix

normalizeEmailAddress in commands/chat.ts now strips the angle-bracket wrapper before lowercasing:

const angleMatch = value.match(/<([^>]+)>/);
const bare = angleMatch?.[1] ?? value;
return bare.trim().toLowerCase();

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:

primitive chat Zork <zork@play.primitive-staging-1.com> --reply '...' ...

< 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 in args.recipient anyway) and the reply context loader matches it cleanly.

Test plan

  • pnpm lint clean
  • pnpm typecheck clean
  • pnpm test 517/517 (added 5 cases on normalizeEmailAddress: 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)
  • After merge + release: re-run the Zork repro (primitive chat zork@play.primitive-staging-1.com "open mailbox" then primitive 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_email and from_header both carry the display-name form on this email, while sender is bare. Naming-wise from_email reads like "bare address" but is actually "from-header form". This CLI fix papers over it; the cleaner long-term fix is making from_email actually 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.

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-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

Fixes a recipient-matching bug where normalizeEmailAddress rejected display-name-wrapped addresses (Zork <zork@example.com>) as different from the bare form (zork@example.com) typed by the user or agent, blocking the primitive chat reply flow for essentially every real service.

  • normalizeEmailAddress now extracts the bare address from inside angle brackets before lowercasing; bare addresses pass through unchanged, so existing callers are unaffected.
  • The function is exported and covered by five new unit tests spanning the relevant address shapes.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to one helper function and does not alter any call sites or data flows.

The regex /<([^>]+)>/ correctly extracts the bare address on the display-name path, and the ?? value fallback keeps bare addresses working as before. Every edge case in the test suite passes. No callers were modified; they simply inherit the corrected normalisation.

No files require special attention.

Important Files Changed

Filename Overview
cli-node/src/oclif/commands/chat.ts Exports normalizeEmailAddress and adds RFC 5322 display-name stripping via a single regex before lowercasing; all callers benefit automatically.
cli-node/tests/oclif/chat.test.ts Adds 5 unit tests for normalizeEmailAddress covering bare address, display-name form, quoted display name, bare angle-bracket wrapper, and the round-trip identity that is the core of the fix.

Reviews (1): Last reviewed commit: "Strip RFC 5322 display name when matchin..." | Re-trigger Greptile

@etbyrd etbyrd added this pull request to the merge queue Jun 1, 2026
Merged via the queue into main with commit adc0d40 Jun 1, 2026
9 checks passed
@etbyrd etbyrd deleted the chat-reply-display-name-match branch June 1, 2026 15:41
@etbyrd etbyrd mentioned this pull request Jun 1, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant