Skip to content

fix(poll): make the poll question visible both directions (echo out, backfill in)#155

Open
omarshahine wants to merge 6 commits into
openclaw:mainfrom
omarshahine:fix/poll-comment-echo
Open

fix(poll): make the poll question visible both directions (echo out, backfill in)#155
omarshahine wants to merge 6 commits into
openclaw:mainfrom
omarshahine:fix/poll-comment-echo

Conversation

@omarshahine

@omarshahine omarshahine commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Native iMessage poll balloons have no titleitem.title is empty and Messages never renders it. The question you see on a native poll lives in a separate caption message whose reply_to is the poll guid (the composer's "comment or Send" field). imsg ignored this on both sides:

  • Outbound: poll send --question 'X' stored X in the invisible item.title, so agent-created polls rendered as bare options with no question.
  • Inbound: a native poll decoded from chat.db had question=nil, so agents saw a title-less Poll — options: ….

Why This Change Was Made

Make the poll question visible in both directions, matching native behavior, with no consumer changes required (OpenClaw already renders 📊 Poll: <question> when the field is present, and passes --question on send).

User Impact

  • Outboundpoll send / poll.send RPC now send --question as a plain caption message right after the poll — associated_message_type = 0, thread_originator_guid = None, i.e. not a threaded reply, so it renders as a normal caption bubble instead of the "reply connector" a threaded reply would draw. --comment overrides the echoed text; the echo is best-effort so a caption failure never fails an already-delivered poll. Agents keep passing only --question.
  • InboundenrichedPollEvent backfills an empty created-poll question from its caption (earliest reply-to-the-poll message), so inbound native polls surface their question. imsg-created polls with a real title are unchanged; vote enrichment untouched.

Evidence

Verified end-to-end on macOS 26 through a live v2 bridge, sending with this branch's build (swift build --product imsg).

Outbound — live send to a test group:

$ imsg poll send --chat-id 22 --question "PR#155 proof: which crustacean?" \
      --option Lobster --option Crab --option Shrimp --json
{"poll":{"kind":"created","question":"PR#155 proof: which crustacean?",
 "options":[Lobster,Crab,Shrimp]},"event":"imessage.poll.created",
 "messageGuid":"EAE66DE7-…","balloonBundleID":"…Polls"}

Visual — confirmed in the Messages UI on the receiving device: the poll shows the three options (Lobster / Crab / Shrimp), and directly beneath it the question appears as an ordinary caption bubble ("PR#155 proof: which crustacean?") — no threaded-reply connector line. (Screenshot available; can be attached to this PR.)

Structural proof (chat.db) — my sent caption is byte-for-byte the same shape as a natively-composed poll's caption, and differs from the earlier threaded-reply attempt that drew the connector:

poll_guid  origin          caption   assoc_type  thread_originator
EAE66DE7   this PR (sent)   01E1505B  0           NULL   ← clean, matches native
2E97B312   native (recv)    CCBD34D4  0           NULL   ← native reference
AC8A8E5B   native (recv)    1ECFCA87  0           NULL   ← native reference
A3DDC24F   old threaded try C615FCEF  100         A3DDC24F ← the rejected reply connector

Note: this bridge auto-populates reply_to_guid on every send (the poll itself got a reply_to although no --reply-to was passed), so both native and PR captions carry reply_to; the connector is driven by thread_originator/assoc_type=100, which the PR caption does not set — matching native exactly.

Inbound — backfill via imsg history (chat.db read, no bridge), native [recv] polls that decoded as question=None now surface their question:

[recv] [poll created] Favorite crustacean friend of a Lobster?: Lobster 🦞 / Crab 🦀 / Shrimp 🦐
[recv] [poll created] What color pill?: Red / Blue
[recv] [poll created] Pick a color: Black / White

Review follow-ups addressed (ClawSweeper P2s): CLI caption echo is now best-effort (do/catch, matching the RPC path); pollCommentText is guarded on schema.hasReplyToGUIDColumn so schemas without the column degrade to nil instead of throwing; poll-send tests record the full bridge call sequence and assert both .sendPoll and the .sendMessage caption echo. macOS + Linux CI green.

🤖 Generated with Claude Code

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed July 5, 2026, 12:52 PM ET / 16:52 UTC.

Summary
The branch sends a best-effort plain caption message after poll creation, backfills empty inbound created-poll questions from native caption rows, adds a --comment override, and updates poll tests.

Reproducibility: yes. from source and contributor proof: current main sends only the poll payload and only enriches vote rows, while the PR head sends a caption and backfills native poll questions. I did not run Messages locally in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Changed surface: 8 files changed, 282 additions, 21 deletions. The patch is focused on poll send/read behavior and matching tests, but it still changes live message-delivery semantics.
  • Current checks: 2 successful checks. GitHub reports both macOS and linux-read-core CI passed on the latest head.

Root-cause cluster
Relationship: canonical
Canonical: #155
Summary: This PR is the canonical implementation for poll caption echo and inbound poll question backfill; the related items are documentation follow-ups or adjacent skill work.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] Merging intentionally changes poll send from one bridge call to a poll plus a best-effort follow-up caption message; if the caption send fails, users can still receive a delivered poll without the visible question.

Maintainer options:

  1. Land the proven caption behavior (recommended)
    Accept the two-message poll send because the caption path is best-effort and the latest head filters inbound backfill to clean native captions.
  2. Require one more UI artifact
    Ask the contributor to attach the promised Messages screenshot or a short recording if visual confirmation is required before merge.
  3. Pause if extra visible sends are unacceptable
    Close or redesign the PR if maintainers do not want poll send to create a second visible message after the poll balloon.

Next step before merge

  • No ClawSweeper repair lane is needed; the remaining action is maintainer acceptance of the intentional two-message poll-send behavior before merge.

Maintainer decision needed

  • Question: Should imsg accept the intentional two-message poll-send behavior where the poll is delivered first and the visible caption is sent best-effort afterward?
  • Rationale: The patch is technically focused and proven, but it changes outbound message-delivery semantics in a way only maintainers can accept for the CLI/RPC contract.
  • Likely owner: steipete — Recent poll helper refactoring and release ownership make this the best available routing candidate for accepting the outbound behavior contract.
  • Options:
    • Land the proven caption behavior (recommended): Accept the two-message behavior because the caption echo is best-effort, structurally matched to native captions, and guarded by threaded-reply regression coverage.
    • Ask for attached UI proof first: Require a screenshot or recording showing the caption bubble without a threaded connector if maintainers want more visual confidence before merge.
    • Reject follow-up message semantics: Pause or close this approach if maintainers do not want poll creation to emit an additional user-visible message.

Security
Cleared: The diff changes local Swift poll handling and tests only; it does not alter dependencies, workflows, secrets, permissions, or artifact execution paths.

Review details

Best possible solution:

Land the focused caption/backfill fix after maintainer acceptance of the two-message poll-send behavior, then use #157 for the skill-doc follow-up.

Do we have a high-confidence way to reproduce the issue?

Yes from source and contributor proof: current main sends only the poll payload and only enriches vote rows, while the PR head sends a caption and backfills native poll questions. I did not run Messages locally in this read-only review.

Is this the best way to solve the issue?

Yes, with maintainer acceptance of the delivery tradeoff. The implementation is narrow, keeps caption echo best-effort to avoid duplicate poll retries, and constrains inbound backfill to native caption-shaped rows with regression coverage.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 9ac97ab1d242.

Label changes

Label changes:

  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body and follow-up comments provide after-fix live CLI output, chat.db structural comparison against native captions, UI confirmation, and inbound history output.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: Poll question visibility affects a bounded but user-facing native iMessage poll workflow.
  • merge-risk: 🚨 message-delivery: The PR changes outbound poll sending into a poll plus follow-up caption message and changes inbound caption rows into agent-visible poll questions.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body and follow-up comments provide after-fix live CLI output, chat.db structural comparison against native captions, UI confirmation, and inbound history output.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and follow-up comments provide after-fix live CLI output, chat.db structural comparison against native captions, UI confirmation, and inbound history output.
Evidence reviewed

What I checked:

Likely related people:

  • veteranbv: Authored the merged native poll support and readback commits that introduced the poll decoding surface this PR extends. (role: native poll feature contributor; confidence: medium; commits: 0c9d85db44f0, 20ed29c35e81, 2529aa7ad011; files: Sources/IMsgCore/MessagePolls.swift, Sources/IMsgCore/MessageStore+Polls.swift, Sources/IMsgCore/MessageStore+ReplyContext.swift)
  • steipete: Recent history shows poll helper refactoring and release stewardship around the affected CLI/RPC and poll files. (role: recent poll command area contributor; confidence: medium; commits: fed89d6b4f9d, 9fe0d92b5dbf; files: Sources/imsg/Commands/PollCommand.swift, Sources/imsg/RPCServer+BridgeMessageHandlers.swift, Sources/IMsgCore/MessageStore+Polls.swift)
  • omarshahine: Authored prior merged native poll voting work that touched the same command, RPC, and enrichment paths, so the author is also connected to current-main poll history beyond this PR. (role: adjacent poll contributor; confidence: medium; commits: 617d941f7b1e; files: Sources/imsg/Commands/PollCommand.swift, Sources/imsg/RPCServer+BridgeMessageHandlers.swift, Sources/IMsgCore/MessageStore+Polls.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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 keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (8 earlier review cycles)
  • reviewed 2026-07-05T00:44:56.189Z sha b3e734a :: needs real behavior proof before merge. :: [P2] Keep the CLI echo best-effort | [P2] Update the poll-send tests for two bridge calls
  • reviewed 2026-07-05T01:22:26.328Z sha a9071de :: needs real behavior proof before merge. :: [P2] Keep the CLI caption send best-effort | [P2] Update poll-send tests for the second bridge call
  • reviewed 2026-07-05T01:41:29.802Z sha f5de557 :: needs real behavior proof before merge. :: [P2] Keep the CLI caption send best-effort | [P2] Update poll-send tests for the second bridge call
  • reviewed 2026-07-05T01:46:08.112Z sha f5de557 :: needs real behavior proof before merge. :: [P2] Keep the CLI caption send best-effort | [P2] Update poll-send tests for the second bridge call
  • reviewed 2026-07-05T15:55:13.239Z sha 2847415 :: needs real behavior proof before merge. :: [P2] Keep the CLI caption send best-effort | [P2] Update poll-send tests for the second bridge call | [P2] Guard caption lookup when reply_to_guid is absent
  • reviewed 2026-07-05T16:07:02.482Z sha 36e1ece :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T16:11:36.188Z sha 36e1ece :: needs changes before merge. :: [P2] Filter out threaded replies when backfilling captions
  • reviewed 2026-07-05T16:48:13.465Z sha 64ab396 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. labels Jul 5, 2026
@omarshahine omarshahine changed the title fix(poll): echo the poll question as a visible comment reply fix(poll): make the poll question visible both directions (echo out, backfill in) Jul 5, 2026
omarshahine and others added 3 commits July 5, 2026 08:50
Messages never renders the poll title (payload item.title) on the poll
balloon — only the options are shown. Agent-created polls therefore arrived
as bare options with no visible question. Native polls carry the question in
a separate comment message whose reply_to is the poll guid (the "comment or
Send" composer field), which imsg never populated.

Both `poll send` (CLI) and the `poll.send` RPC now echo `--question` as a
reply comment on the poll after creation, matching native behavior. Callers
pass only the question and the visible caption appears for free, so agents
(e.g. OpenClaw) need no change. `--comment` overrides the echoed text; the
poll payload title is still set for agent readback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The echoed caption was sent with selectedMessageGuid, which builds a full
threaded reply (thread_originator + associated_message_type 100) and makes
Messages decorate the poll balloon with a reply-connector line. Native poll
comments carry NO thread metadata (reply_to only, type 0), rendering as a
clean caption.

Send the caption as a plain message right after the poll instead. Outbound
(from_me) rows are cached and never re-processed by the monitor, so the
poll<->comment link was never needed on our own sends; inbound user polls
still carry native reply_to for folding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inverse of the outbound echo. Native poll balloons have no title
(item.title empty), so an inbound native poll decoded from chat.db had
question=nil and rendered to agents as a title-less "Poll — options: …".
The question is carried in the poll's caption — a separate message whose
reply_to_guid is the poll guid.

enrichedPollEvent now backfills an empty created-poll question from that
caption (earliest reply-to-the-poll message, decoded via decodeMessageRow
for the attributedBody fallback). imsg-created polls that already carry a
title are unchanged; vote enrichment is untouched.

Verified against chat.db: native polls that were question=None now surface
"Favorite crustacean friend of a Lobster?", "What color pill?", etc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@omarshahine omarshahine force-pushed the fix/poll-comment-echo branch from f5de557 to 2847415 Compare July 5, 2026 15:51
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Jul 5, 2026
omarshahine and others added 2 commits July 5, 2026 08:57
Two CI failures surfaced once this branch ran CI for the first time:

- pollCommentText hardcoded WHERE m.reply_to_guid, which throws on older or
  synthetic message tables that omit the column. Gate it on
  schema.hasReplyToGUIDColumn (mirroring MessageRowSelection) so a created
  poll with no caption column degrades to nil instead of failing the pull.
- The question echo adds a second .sendMessage bridge call, which clobbered
  the single-capture mocks in the poll-send tests. Record all calls and
  assert both the .sendPoll and the .sendMessage caption echo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dTDUtxanzs8NDhTD862QV
If the poll delivered but the follow-up caption send throws, the CLI must
not exit nonzero — a retry would send a duplicate poll. Wrap the echo in
do/catch and report on stderr, matching handlePollSend. Closes the last
ClawSweeper P2 on this branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dTDUtxanzs8NDhTD862QV
@clawsweeper clawsweeper Bot removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 5, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed all three P2 findings and added real behavior proof:

  • CLI caption echo is now best-effort (do/catch, matching the RPC path) — a caption failure no longer exits nonzero after the poll delivered (36e1ece).
  • pollCommentText is guarded on schema.hasReplyToGUIDColumn; a table without the column degrades to nil instead of throwing (8b8f451).
  • Poll-send tests now record the full bridge call sequence and assert both .sendPoll and the .sendMessage caption echo (8b8f451).

Proof (PR body updated): live imsg poll send through a v2 bridge on macOS 26 using this branch's build; chat.db shows the sent caption is structurally identical to a natively-composed poll's caption (assoc_type=0, thread_originator=None) and differs from the old threaded-reply attempt (assoc_type=100, thread_originator set). Inbound native [recv] polls now surface their backfilled questions via imsg history. macOS + Linux CI green.

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. labels Jul 5, 2026
omarshahine added a commit to omarshahine/imsg that referenced this pull request Jul 5, 2026
The poll caption echo, --comment override, and inbound question backfill
live in openclaw#155, not on main (shipped imsg 0.12.2 'poll --help' has no
--comment). Drop those claims so the skill documents only shipped
behavior and can merge independently of openclaw#155. Tracked by openclaw#157 to re-add
once openclaw#155 lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dTDUtxanzs8NDhTD862QV
ClawSweeper P1: pollCommentText matched any reply_to_guid row, so a
threaded inline reply to the poll (associated_message_type 100, thread
originator set) could be mistaken for the poll question. Filter to clean
native captions (type 0, no thread originator), schema-aware since both
columns are optional. Adds regression coverage: a clean caption still
backfills; a threaded reply is ignored and the question stays empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dTDUtxanzs8NDhTD862QV
@omarshahine

Copy link
Copy Markdown
Contributor Author

Fixed the P1 (64ab396): pollCommentText now filters to clean native captions (associated_message_type = 0 AND thread_originator_guid IS NULL, schema-aware since both columns are optional), so a threaded inline reply to the poll can no longer be mistaken for the question. Added regression coverage: messageStorePollBackfillsQuestionFromCleanCaption (clean caption still backfills) and messageStorePollBackfillIgnoresThreadedReply (threaded reply ignored, question stays empty). macOS + Linux green.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot removed the rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. label Jul 5, 2026
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 5, 2026
omarshahine added a commit to openclaw/openclaw that referenced this pull request Jul 5, 2026
Drop the poll caption echo, --comment override, and inbound question
backfill from the imsg skill: those live in imsg PR openclaw/imsg#155,
not shipped imsg 0.12.2 (poll --help exposes only --question/--option).
Also removes the inaccurate 'reply comment whose reply_to is the poll
guid' framing — #155 sends the caption as a plain message, not a reply.
Keeps shipped poll send/vote usage. Re-add once a release ships #155.
omarshahine added a commit to openclaw/openclaw that referenced this pull request Jul 5, 2026
* docs: tailor imsg skill for OpenClaw agents

* docs(imsg-skill): document that poll send echoes --question as a visible comment

* docs(imsg-skill): note inbound native poll question is backfilled from its caption

* docs(imsg-skill): document only shipped poll behavior

Drop the poll caption echo, --comment override, and inbound question
backfill from the imsg skill: those live in imsg PR openclaw/imsg#155,
not shipped imsg 0.12.2 (poll --help exposes only --question/--option).
Also removes the inaccurate 'reply comment whose reply_to is the poll
guid' framing — #155 sends the caption as a plain message, not a reply.
Keeps shipped poll send/vote usage. Re-add once a release ships #155.

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant