Skip to content

fix(poll): stamp vote balloon/payload on each responding target (macOS 26.4 regression)#150

Merged
steipete merged 1 commit into
openclaw:mainfrom
omarshahine:fix/poll-vote-stamp-macos26
Jul 2, 2026
Merged

fix(poll): stamp vote balloon/payload on each responding target (macOS 26.4 regression)#150
steipete merged 1 commit into
openclaw:mainfrom
omarshahine:fix/poll-vote-stamp-macos26

Conversation

@omarshahine

Copy link
Copy Markdown
Contributor

Problem

Casting a vote on a native Messages poll silently produced a vote with no balloon payload on macOS 26.4.x, so the associated 4000 message shipped (is_sent=1) but never delivered (is_delivered=0) and never registered on the recipient's poll.

Cause

buildPollVoteIMMessage stamps balloonBundleID + payloadData onto the message after constructing it with the association-atomic initializer. The stamping loop required a single target to respond to both setters, otherwise it continued:

for (id target in targets) {
    if (![target respondsToSelector:@selector(setBalloonBundleID:)]
        || ![target respondsToSelector:@selector(setPayloadData:)]) continue;
    [target setBalloonBundleID:balloonID];
    [target setPayloadData:payloadData];
    return result;
}
return nil;

On macOS 26.4.x the backing _imMessageItem does not respond to both, so the loop fell through to the transient IMMessage wrapper — whose stamped values do not persist to the serialized item. The vote was emitted without the payload.

Fix

Stamp each setter on every target that responds to it (item and/or wrapper), so both values land on whatever object serializes. Fail only if a value could not be stamped anywhere:

BOOL balloonStamped = NO, payloadStamped = NO;
for (id target in targets) {
    if ([target respondsToSelector:@selector(setBalloonBundleID:)]) { ...; balloonStamped = YES; }
    if ([target respondsToSelector:@selector(setPayloadData:)])     { ...; payloadStamped = YES; }
}
return (balloonStamped && payloadStamped) ? result : nil;

Evidence (macOS 26.4.1)

chat.db, same machine, before vs after:

before:  VOTE(4000)  is_delivered=0  payload=NONE   → never registered
after:   VOTE(4000)  is_delivered=1  payload=7333b  → registers on the poll

Confirmed live: the vote now carries the 7333-byte Polls payload, delivers, and is associated to the poll (bare poll GUID).

@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 5:42 AM ET / 09:42 UTC.

Summary
The branch changes the injected poll-vote message builder to stamp balloon and payload metadata independently across the IMMessage wrapper and backing item, with source-inspection regression coverage plus docs and release-note updates.

Reproducibility: no. independent high-confidence reproduction was run in this Linux read-only review environment. Current main shows the exact one-target check and the PR supplies live macOS 26.4.1 before/after chat.db output for the failing and fixed paths.

Review metrics: 3 noteworthy metrics.

  • Diff surface: 4 files changed, +32/-21. The patch is narrow, but it touches the injected helper path that determines native poll-vote delivery.
  • Runtime proof surface: 1 live macOS 26.4.1 before/after proof. The supplied database output covers the reported regression line while leaving broader OS behavior to maintainer judgment.
  • Current CI: 2 successful check runs. The macos and linux-read-core checks passed on the PR head, supplementing but not replacing live Messages proof.

Root-cause cluster
Relationship: canonical
Canonical: #150
Summary: This PR is the active same-repository fix for the poll-vote delivery regression; the merged poll feature PRs are provenance, not replacement 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:

  • Capture maintainer or Mantis final-head macOS poll-vote proof if maintainers want independent runtime confirmation.

Mantis proof suggestion
A redacted native Messages poll-vote proof would materially help verify this private macOS runtime delivery path beyond source review and CI. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify imsg poll vote casts a native Messages poll vote and capture redacted chat.db delivery plus rendered poll state.

Risk before merge

  • [P1] The fixed path depends on private IMCore object behavior, so CI and source-inspection tests cannot fully prove selector persistence across macOS versions.
  • [P1] This review did not independently run Messages on macOS 26.4.1; confidence comes from source inspection plus the contributor's redacted live database output.
  • [P1] Because the diff changes native poll-vote message construction immediately before send, an incorrect target-stamping assumption could keep vote rows local-only, undelivered, or unregistered.

Maintainer options:

  1. Accept the targeted poll-vote fix (recommended)
    Maintainers can accept the supplied live macOS 26.4.1 proof and focused diff, then merge after normal review and branch checks.
  2. Request final-head macOS proof
    If maintainers want independent runtime confidence, ask for a final-head run showing a redacted vote row with payload bytes, delivered state, and rendered poll registration.
  3. Pause for selector matrix proof
    If split-target stamping is too private-runtime-specific, hold the PR until the supported object/selector behavior is documented across the macOS versions maintainers care about.

Next step before merge

  • No automated repair is needed; maintainer review should decide whether the supplied private-runtime proof is enough to merge.

Security
Cleared: No security or supply-chain concern was found; the diff only changes the Objective-C helper path, source-inspection tests, docs, and a release note with no dependency, workflow, secret, permission, or download changes.

Review details

Best possible solution:

Land the narrow helper fix after maintainer review accepts the supplied live macOS 26.4.1 proof, preserving fail-closed construction when either required field cannot be stamped anywhere.

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

No independent high-confidence reproduction was run in this Linux read-only review environment. Current main shows the exact one-target check and the PR supplies live macOS 26.4.1 before/after chat.db output for the failing and fixed paths.

Is this the best way to solve the issue?

Yes: this is a narrow repair to the shipped private bridge helper rather than a new API, config path, or behavior mode. The main remaining question is maintainer acceptance of the private-runtime proof boundary.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 9452489d8f2e.

Label changes

Label justifications:

  • P2: This fixes a shipped native poll-vote delivery regression with limited blast radius to poll voting.
  • merge-risk: 🚨 message-delivery: The diff changes how native Messages poll-vote payload fields are stamped before send, which can affect whether vote messages deliver and register.
  • 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 comment include redacted live macOS 26.4.1 chat.db before/after output showing payload delivery and poll registration after the fix.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and follow-up comment include redacted live macOS 26.4.1 chat.db before/after output showing payload delivery and poll registration after the fix.
Evidence reviewed

What I checked:

  • Repository policy read: The full target AGENTS.md was read; its focused changeset, verification, and macOS Messages permission guidance applies to this private bridge review. (AGENTS.md:1, 9452489d8f2e)
  • Current main behavior: Current main still only stamps poll-vote balloon and payload metadata when a single target responds to both setters, then returns nil if no such target is found. (Sources/IMsgHelper/IMsgInjected.m:2871, 9452489d8f2e)
  • Shipped release still has the old path: The v0.12.1 source snapshot contains the same one-target setter requirement, so the fix is not already shipped in the latest release. (Sources/IMsgHelper/IMsgInjected.m:2871, 2c1021ac39f3)
  • PR implementation: The PR head tracks balloonStamped and payloadStamped independently while iterating the backing item and wrapper, then succeeds only if both values were accepted somewhere. (Sources/IMsgHelper/IMsgInjected.m:2863, 4b943dc3fd34)
  • Regression coverage shape: The updated source-inspection test checks for independent balloon/payload stamping and guards against reintroducing the combined respondsToSelector condition. (Tests/imsgTests/BridgeCommandRegistrationTests.swift:171, 4b943dc3fd34)
  • Real behavior proof: The PR body and follow-up comment provide redacted live macOS 26.4.1 chat.db before/after output: pre-fix vote rows had no payload and were not delivered; after the fix, the vote carried a 7333-byte payload, delivered, and registered on the poll. (4b943dc3fd34)

Likely related people:

  • steipete: Prior merged history hardened and exposed the poll-vote bridge capability, and the latest release snapshot/current branch history is tied to this helper and tests. (role: recent poll-vote hardening and release-area contributor; confidence: high; commits: 8dcdaea5c322, 4f93a4a29c01, 2c1021ac39f3; files: Sources/IMsgHelper/IMsgInjected.m, Sources/imsg/Commands/PollCommand.swift, Tests/imsgTests/BridgeCommandRegistrationTests.swift)
  • omarshahine: Prior merged history added the outgoing poll-vote CLI/RPC/bridge path that this PR repairs, beyond authorship of this PR. (role: poll-vote feature contributor; confidence: high; commits: 2ca0fadd8453, c56c24d488ef, 2d7b506d1736; files: Sources/IMsgHelper/IMsgInjected.m, Sources/imsg/Commands/PollCommand.swift, Sources/IMsgCore/IMsgBridgeProtocol.swift)
  • veteranbv: Earlier merged native poll work introduced the poll decoding/sending foundation that the vote sender builds on. (role: native poll feature introducer; confidence: medium; commits: 0c9d85db44f0, 9a21cdbe9017, 2529aa7ad011; files: Sources/IMsgHelper/IMsgInjected.m, Sources/IMsgCore/MessagePolls.swift, Tests/IMsgCoreTests/MessagePollTests.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.

@clawsweeper clawsweeper Bot added 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. 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 2, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

Live proof (macOS 26.4.1)

Same machine, same poll flow, from chat.db — a me poll vote (associated_message_type=4000), before and after this fix:

before (v0.12.0):  VOTE(4000)  is_sent=1  is_delivered=0  payload_data=NULL    → never registered
after  (this fix): VOTE(4000)  is_sent=1  is_delivered=1  payload_data=7333b   → registers on the poll
                   assoc_message_guid = <poll guid>, balloon_bundle_id = …messages.Polls

The pre-fix vote shipped with no Polls payload and never delivered, so it did not appear on the recipient's poll. After stamping each setter on every responding target, the vote carries the 7333-byte Polls payload, delivers, and is associated to the poll — confirmed registering on the recipient device across repeated sends.

omarshahine added a commit to openclaw/openclaw that referenced this pull request Jul 2, 2026
…r comment fold

Makes native iMessage polls behave correctly end to end.

What changed:
- Inbound polls render with a numbered-options vote cue so the agent casts a native vote instead of answering the poll in prose.
- poll-vote resolves the poll reference from pollId/pollGuid/messageId and now defaults to the current inbound poll message when the model omits it; still errors when no reference exists.
- poll-vote echo suppression is session-scoped, so the redundant spoken answer is dropped across the separate poll and comment runs.
- A poll's inline-reply caption is folded (not delivered as a standalone question) only when the poll creator and reply sender are both known and equal; unknown/mismatched sender falls through to the normal inbound decision gate, so no inbound reply is silently dropped.

Evidence:
- 64 passing tests in the poll suites (poll-comment, poll-render, actions), incl. sender fail-closed regressions; pnpm build clean.
- Two Codex autoreviews clean (patch is correct); ClawSweeper re-review rated it platinum hermit.
- Live-verified on macOS 26.4.1 on the deployed gateway: poll "What color pill?" -> native vote delivered with a 7333-byte payload, caption folded, zero echo.

Note: vote delivery also depends on the imsg vote-stamp fix (openclaw/imsg#150); OpenClaw ships ahead of imsg per owner decision.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 2, 2026
…r comment fold

Makes native iMessage polls behave correctly end to end.

What changed:
- Inbound polls render with a numbered-options vote cue so the agent casts a native vote instead of answering the poll in prose.
- poll-vote resolves the poll reference from pollId/pollGuid/messageId and now defaults to the current inbound poll message when the model omits it; still errors when no reference exists.
- poll-vote echo suppression is session-scoped, so the redundant spoken answer is dropped across the separate poll and comment runs.
- A poll's inline-reply caption is folded (not delivered as a standalone question) only when the poll creator and reply sender are both known and equal; unknown/mismatched sender falls through to the normal inbound decision gate, so no inbound reply is silently dropped.

Evidence:
- 64 passing tests in the poll suites (poll-comment, poll-render, actions), incl. sender fail-closed regressions; pnpm build clean.
- Two Codex autoreviews clean (patch is correct); ClawSweeper re-review rated it platinum hermit.
- Live-verified on macOS 26.4.1 on the deployed gateway: poll "What color pill?" -> native vote delivered with a 7333-byte payload, caption folded, zero echo.

Note: vote delivery also depends on the imsg vote-stamp fix (openclaw/imsg#150); OpenClaw ships ahead of imsg per owner decision.
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
@steipete steipete force-pushed the fix/poll-vote-stamp-macos26 branch from a88b367 to 4b943dc Compare July 2, 2026 09:36
@steipete

steipete commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Maintainer proof for exact head 4b943dc3fd34c0a7124a5ef6bbc3e9ffd9249144:

  • Regression: the focused source-contract test failed on current main before the fix and passes after it.
  • Local: make lint (0 serious findings), make test (365 tests), make build-dylib, and make build passed.
  • Source-blind runtime: on macOS 26.5.2, a standalone IMCore probe constructed the associated-message object and backing item twice; both poll metadata values were stamped and observable. It did not access a chat or account, and it sent nothing.
  • macOS 26.4 delivery: the contributor's redacted 26.4.1 before/after proof shows the fixed vote carries the Polls payload, delivers, and registers. The maintainer accepted an item-specific waiver for an independent 26.4 rerun because the available hosts were macOS 26.5 with SIP enabled.
  • Structured autoreview: clean; no accepted or actionable findings.
  • Exact-head CI: run 28580316947macOS and Linux passed.

Contributor credit is retained in the commit trailer and changelog.

@steipete steipete merged commit 1560a67 into openclaw:main Jul 2, 2026
2 checks passed
@steipete

steipete commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Landed as 1560a678ce0ff8a273ab0bac5848737c1a64f4e0.

Final proof for the landed tree:

  • Local commands passed: make lint (0 serious findings), make test (365 tests), make build-dylib, and make build.
  • The focused regression failed against pre-fix main and passed with the fix.
  • Exact PR head 4b943dc3fd34c0a7124a5ef6bbc3e9ffd9249144 passed both macOS and Linux CI.
  • Final structured autoreview reported no accepted or actionable findings.
  • A source-blind runtime probe twice confirmed both Polls metadata values across the associated message and backing item on macOS 26.5.2 without reading or sending private message data.
  • Caveat: the maintainer accepted an item-specific waiver for an independent macOS 26.4 delivery rerun; the contributor's redacted macOS 26.4.1 before/after delivery proof was retained as that platform's live evidence.

Contributor credit is present in the squash commit and changelog. No release was performed.

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.

2 participants