fix(imessage): shed emoji anywhere in poll-vote echo match#98691
Conversation
iMessage native poll options carry a trailing emoji ("Lobster 🦞 ") while the
agent echoes its vote with a leading one ("🦞 Lobster.") under message_tool_only
reply mode. normalizePollEchoText stripped only a leading emoji prefix, so
"lobster 🦞" never matched "lobster" and the redundant text leaked past the
poll_vote_echo guard (shipped in openclaw#98421).
Shed emoji anywhere across every class: pictographic, regional-indicator flags
(🇺🇸), subdivision-flag tag chars, ZWJ, skin-tone, and keycap sequences cleared
as a unit (so "1️⃣" -> "" not "1", while a plain "1"/"#"/"*" survives). Internal
punctuation stays so C#/C++/Node.js remain distinct; emoji-only labels normalize
to empty and the guard's existing empty-option check fails open. Re-exports
normalizePollEchoText with a focused unit test across every emoji class plus a
message-tool integration test for the emoji-suffixed-option case.
|
Codex review: needs real behavior proof before merge. Reviewed July 1, 2026, 2:29 PM ET / 18:29 UTC. Summary Reproducibility: yes. Current main strips only a leading emoji prefix, so source inspection and a direct normalization probe show Review metrics: none identified. Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge the narrow matcher after inspectable redacted iMessage proof, or maintainer-owned live verification, confirms emoji-option echo suppression and a non-matching send that remains visible. Do we have a high-confidence way to reproduce the issue? Yes. Current main strips only a leading emoji prefix, so source inspection and a direct normalization probe show Is this the best way to solve the issue? Yes for the code shape: the helper stays in the message-tool owner boundary and preserves emoji identity while ignoring placement. It is not merge-ready until the live iMessage behavior has inspectable proof. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 53fe2e4bc3ca. Label changesLabel justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
ClawSweeper/CI check-lint flagged no-misleading-character-class: combining
marks (VS16, U+20E3), ZWJ, and skin-tone modifiers are misleading inside a
single character class. Rewrite the emoji strip as an alternation using
property escapes (\p{Regional_Indicator}, \p{Emoji_Modifier}) plus standalone
atoms so the same normalization behavior passes lint. Behavior unchanged;
poll-echo unit + integration tests still green.
|
Land-ready maintainer pass complete at exact head Improvements:
Proof:
Live native Messages poll proof is unavailable on this host because SIP prevents IMCore helper injection. The touched matcher behavior is covered by focused tests and the exact-head hosted gates above. |
|
Merged via squash.
|
…98691) * fix(imessage): shed emoji anywhere in poll-vote echo match iMessage native poll options carry a trailing emoji ("Lobster 🦞 ") while the agent echoes its vote with a leading one ("🦞 Lobster.") under message_tool_only reply mode. normalizePollEchoText stripped only a leading emoji prefix, so "lobster 🦞" never matched "lobster" and the redundant text leaked past the poll_vote_echo guard (shipped in openclaw#98421). Shed emoji anywhere across every class: pictographic, regional-indicator flags (🇺🇸), subdivision-flag tag chars, ZWJ, skin-tone, and keycap sequences cleared as a unit (so "1️⃣" -> "" not "1", while a plain "1"/"#"/"*" survives). Internal punctuation stays so C#/C++/Node.js remain distinct; emoji-only labels normalize to empty and the guard's existing empty-option check fails open. Re-exports normalizePollEchoText with a focused unit test across every emoji class plus a message-tool integration test for the emoji-suffixed-option case. * fix(imessage): make poll-echo emoji regex lint-safe ClawSweeper/CI check-lint flagged no-misleading-character-class: combining marks (VS16, U+20E3), ZWJ, and skin-tone modifiers are misleading inside a single character class. Rewrite the emoji strip as an alternation using property escapes (\p{Regional_Indicator}, \p{Emoji_Modifier}) plus standalone atoms so the same normalization behavior passes lint. Behavior unchanged; poll-echo unit + integration tests still green. * fix(imessage): preserve poll option emoji identity --------- Co-authored-by: Omar Shahine <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…98691) * fix(imessage): shed emoji anywhere in poll-vote echo match iMessage native poll options carry a trailing emoji ("Lobster 🦞 ") while the agent echoes its vote with a leading one ("🦞 Lobster.") under message_tool_only reply mode. normalizePollEchoText stripped only a leading emoji prefix, so "lobster 🦞" never matched "lobster" and the redundant text leaked past the poll_vote_echo guard (shipped in openclaw#98421). Shed emoji anywhere across every class: pictographic, regional-indicator flags (🇺🇸), subdivision-flag tag chars, ZWJ, skin-tone, and keycap sequences cleared as a unit (so "1️⃣" -> "" not "1", while a plain "1"/"#"/"*" survives). Internal punctuation stays so C#/C++/Node.js remain distinct; emoji-only labels normalize to empty and the guard's existing empty-option check fails open. Re-exports normalizePollEchoText with a focused unit test across every emoji class plus a message-tool integration test for the emoji-suffixed-option case. * fix(imessage): make poll-echo emoji regex lint-safe ClawSweeper/CI check-lint flagged no-misleading-character-class: combining marks (VS16, U+20E3), ZWJ, and skin-tone modifiers are misleading inside a single character class. Rewrite the emoji strip as an alternation using property escapes (\p{Regional_Indicator}, \p{Emoji_Modifier}) plus standalone atoms so the same normalization behavior passes lint. Behavior unchanged; poll-echo unit + integration tests still green. * fix(imessage): preserve poll option emoji identity --------- Co-authored-by: Omar Shahine <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…98691) * fix(imessage): shed emoji anywhere in poll-vote echo match iMessage native poll options carry a trailing emoji ("Lobster 🦞 ") while the agent echoes its vote with a leading one ("🦞 Lobster.") under message_tool_only reply mode. normalizePollEchoText stripped only a leading emoji prefix, so "lobster 🦞" never matched "lobster" and the redundant text leaked past the poll_vote_echo guard (shipped in openclaw#98421). Shed emoji anywhere across every class: pictographic, regional-indicator flags (🇺🇸), subdivision-flag tag chars, ZWJ, skin-tone, and keycap sequences cleared as a unit (so "1️⃣" -> "" not "1", while a plain "1"/"#"/"*" survives). Internal punctuation stays so C#/C++/Node.js remain distinct; emoji-only labels normalize to empty and the guard's existing empty-option check fails open. Re-exports normalizePollEchoText with a focused unit test across every emoji class plus a message-tool integration test for the emoji-suffixed-option case. * fix(imessage): make poll-echo emoji regex lint-safe ClawSweeper/CI check-lint flagged no-misleading-character-class: combining marks (VS16, U+20E3), ZWJ, and skin-tone modifiers are misleading inside a single character class. Rewrite the emoji strip as an alternation using property escapes (\p{Regional_Indicator}, \p{Emoji_Modifier}) plus standalone atoms so the same normalization behavior passes lint. Behavior unchanged; poll-echo unit + integration tests still green. * fix(imessage): preserve poll option emoji identity --------- Co-authored-by: Omar Shahine <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…98691) * fix(imessage): shed emoji anywhere in poll-vote echo match iMessage native poll options carry a trailing emoji ("Lobster 🦞 ") while the agent echoes its vote with a leading one ("🦞 Lobster.") under message_tool_only reply mode. normalizePollEchoText stripped only a leading emoji prefix, so "lobster 🦞" never matched "lobster" and the redundant text leaked past the poll_vote_echo guard (shipped in openclaw#98421). Shed emoji anywhere across every class: pictographic, regional-indicator flags (🇺🇸), subdivision-flag tag chars, ZWJ, skin-tone, and keycap sequences cleared as a unit (so "1️⃣" -> "" not "1", while a plain "1"/"#"/"*" survives). Internal punctuation stays so C#/C++/Node.js remain distinct; emoji-only labels normalize to empty and the guard's existing empty-option check fails open. Re-exports normalizePollEchoText with a focused unit test across every emoji class plus a message-tool integration test for the emoji-suffixed-option case. * fix(imessage): make poll-echo emoji regex lint-safe ClawSweeper/CI check-lint flagged no-misleading-character-class: combining marks (VS16, U+20E3), ZWJ, and skin-tone modifiers are misleading inside a single character class. Rewrite the emoji strip as an alternation using property escapes (\p{Regional_Indicator}, \p{Emoji_Modifier}) plus standalone atoms so the same normalization behavior passes lint. Behavior unchanged; poll-echo unit + integration tests still green. * fix(imessage): preserve poll option emoji identity --------- Co-authored-by: Omar Shahine <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
Native iMessage poll support merged in #98421, including a
poll_vote_echoguardthat suppresses the redundant one-word text the agent tends to send right after
voting (it already shows on the poll) under
message_tool_onlyreply mode.That guard leaks for any poll whose option carries an emoji. iMessage stores
native poll options with a trailing emoji, e.g.
"Lobster 🦞 ", while theagent echoes its vote with a leading emoji, e.g.
"🦞 Lobster.". The guard'snormalizePollEchoTextstripped only a leading emoji prefix, so the optionnormalized to
"lobster 🦞"but the echo to"lobster"— no match, and theredundant text went out anyway. Plain-word options (
"Blue") were unaffected,which is why it slipped through review.
Confirmed from live
chat.dbon macOS 26.4.1: the option balloon decoded to["Lobster 🦞 ", "Crab 🦀 ", "Shrimp 🦐 "], and the leaked reply row decoded to"🦞 Lobster.".Why This Change Was Made
The normalizer now sheds emoji anywhere, not just a leading prefix, across
every emoji class so both sides of the comparison shed them symmetrically:
not
Extended_Pictographic, so they need their own ranges1️⃣) cleared as a unit, so an emoji-only keycap labelnormalizes to empty instead of leaving a stray
"1"Internal punctuation is preserved, so
C#,C++,Node.jsstay distinct. Anemoji-only label normalizes to empty and the guard's existing non-empty-option
check fails open (never wrongly suppresses).
User Impact
iMessage's own UI encourages emoji options) no longer produces a redundant
one-word text next to the vote.
adds content beyond the bare option label.
Evidence
src/agents/tools/message-tool.poll-echo.test.ts— 5 passing,covering the emoji-suffix/prefix match, regional-indicator flags, subdivision
tags, ZWJ/skin-tone, keycap-as-unit, keycap-empty, plain-digit/
#preservation,and
C#/C++/Node.js.src/agents/tools/message-tool.test.ts"poll vote echo guard" — 8 passing,including a new integration test casting a vote whose resolved option is
"Lobster 🦞 "then sending"🦞 Lobster."and assertingstatus:suppressed,reason:poll_vote_echo.pnpm tsgo:core— clean.after hardening, re-review READY with no remaining findings.
the
"🦞 Lobster."echo end-to-end.Before/after proof (real data, both function versions run verbatim)
Runs the actual
normalizePollEchoTextsource fromorigin/main(buggy) andthis branch, against the exact strings captured from
chat.db(poll balloon"Lobster 🦞 ", leaked reply"🦞 Lobster."), through the guard's realcomparison (
outboundText && norm(option) && norm(text) === norm(option)):The plain-word control suppresses on both, which is why only emoji options failed
and the regression slipped
#98421review. Distinct options never suppress oneither, confirming no new false positives.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MinTomscE31ajaCggnYu2A