Skip to content

fix(imessage): avoid visible media placeholder text#81209

Merged
omarshahine merged 2 commits into
openclaw:mainfrom
homer-byte:fix/imessage-media-only-no-placeholder
May 13, 2026
Merged

fix(imessage): avoid visible media placeholder text#81209
omarshahine merged 2 commits into
openclaw:mainfrom
homer-byte:fix/imessage-media-only-no-placeholder

Conversation

@homer-byte

@homer-byte homer-byte commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • stop synthesizing literal <media:image> / <media:...> text for iMessage media-only sends
  • send attachment-only iMessages with empty text plus the file path instead
  • add regression coverage for media-only sends and literal <media:image> text when no attachment is present

User problem

When OpenClaw sends an image over native iMessage, the recipient can see the actual image plus a visible <media:image> placeholder in the conversation. In Rich's real iMessage thread this made image sends look duplicated, with the placeholder text appearing between two image previews.

That placeholder is useful as an internal inbound/context marker, but it should not be fabricated as outbound user-visible text when a real file is already being sent.

Fix

sendMessageIMessage now keeps media-only outbound text empty after resolving the attachment. The RPC still receives the file path, so the image is delivered, but OpenClaw no longer sends a synthetic <media:image> text body alongside it.

Literal user text such as literal <media:image> text is still preserved when no attachment is sent.

Real behavior proof

  • Behavior or issue addressed: Native iMessage image sends from OpenClaw can show the image plus a visible <media:image> placeholder, creating duplicate-looking image delivery in the Messages thread.
  • Real environment tested: Local OpenClaw checkout on macOS 26/Darwin 25.4.0, branch fix/imessage-media-only-no-placeholder, executing the patched extensions/imessage/src/send.ts outbound iMessage helper with the real package/test runtime and an instrumented iMessage RPC client.
  • Exact steps or command run after this patch:
cd /Users/openclaw/.openclaw/workspace-homer/tmp/openclaw-imessage-media-placeholder-fix
pnpm exec tsx --conditions=development --tsconfig test/tsconfig/tsconfig.extensions.test.json <<'NODE'
import { sendMessageIMessage } from './extensions/imessage/src/send.ts';
const calls = [];
const client = {
  async request(method, params) {
    calls.push({ method, params });
    return { guid: 'local-after-fix-proof' };
  },
  async stop() {},
};
const result = await sendMessageIMessage('chat_guid:local-proof-chat', '', {
  config: { channels: { imessage: { accounts: { default: {} } } } },
  client,
  mediaUrl: '/tmp/proof-image.png',
  resolveAttachmentImpl: async () => ({ path: '/tmp/proof-image.png', contentType: 'image/png' }),
});
console.log(JSON.stringify({
  sentText: result.sentText,
  rpcMethod: calls[0]?.method,
  rpcText: calls[0]?.params?.text,
  rpcFile: calls[0]?.params?.file,
  receiptKind: result.receipt.parts[0]?.kind,
}, null, 2));
NODE
  • Evidence after fix: Terminal output from the command above:
{
  "sentText": "",
  "rpcMethod": "send",
  "rpcText": "",
  "rpcFile": "/tmp/proof-image.png",
  "receiptKind": "media"
}
  • Observed result after fix: The patched iMessage send path now calls the outbound RPC with file: "/tmp/proof-image.png" and text: ""; it no longer sends the synthetic <media:image> body while still producing a media receipt.
  • What was not tested: A live end-to-end iMessage send from a Gateway running this branch was not performed. The reported before behavior came from Rich's real iMessage thread; the after-fix proof exercises the patched outbound send helper and captures the RPC payload that would be sent.

Tests

  • pnpm exec oxfmt --check --threads=1 extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts
  • pnpm exec oxlint extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts
  • pnpm test extensions/imessage/src/send.test.ts
  • pnpm test extensions/imessage → 354 passed
  • pnpm check:changed

@openclaw-barnacle openclaw-barnacle Bot added channel: imessage Channel integration: imessage size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 12, 2026
@clawsweeper

clawsweeper Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR changes iMessage media-only outbound sends so the RPC receives an empty visible text body while preserving an internal echo key for self-echo suppression.

Reproducibility: yes. Current main source and tests show the media-only image path rewrites empty text to <media:image>, and the discussion includes an independent native iMessage before-reproduction on v2026.5.12-beta.3.

Real behavior proof
Needs stronger real behavior proof before merge: The PR body shows terminal output from an instrumented RPC client, but it does not show a live after-fix native iMessage delivery or Messages.app-visible result; screenshots, a recording, terminal/live output, or redacted logs from a real send would satisfy this, and updating the PR body should trigger re-review or a maintainer can comment @clawsweeper re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Do not queue a repair job; the remaining blocker is real after-fix iMessage proof or a maintainer proof override, not a narrow code repair.

Security
Cleared: The diff is localized to iMessage logic, tests, and changelog text, with no concrete security or supply-chain regression found.

Review details

Best possible solution:

Merge the code shape after live after-fix iMessage proof or an explicit maintainer proof override: keep RPC text empty for media-only sends and keep echoText as the internal cache key.

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

Yes. Current main source and tests show the media-only image path rewrites empty text to <media:image>, and the discussion includes an independent native iMessage before-reproduction on v2026.5.12-beta.3.

Is this the best way to solve the issue?

Yes for the code direction after the follow-up. The patch now decouples visible RPC text from internal echo matching, which is the narrow maintainable fix for the visible-placeholder bug.

What I checked:

  • Current main sends the placeholder as outbound text: On current main, attachment resolution rewrites empty media text through resolveDeliveredIMessageText, which returns <media:image> for image content and assigns that value to the RPC text parameter. (extensions/imessage/src/send.ts:198, 1d6e5f7a3e42)
  • Current main uses sent text for echo caches: The iMessage delivery path records sent.sentText in the in-memory echo cache for text and media sends, so replacing the visible placeholder without another key would have regressed self-echo dedupe. (extensions/imessage/src/monitor/deliver.ts:73, 1d6e5f7a3e42)
  • Current tests cover media-placeholder echo fallback: Current inbound-processing coverage explicitly expects attachment-only echoes to match by body text placeholder, confirming the internal marker is part of the echo-dedupe contract. (extensions/imessage/src/monitor/inbound-processing.test.ts:91, 1d6e5f7a3e42)
  • PR head keeps visible text empty and creates an echo key: At PR head, sendMessageIMessage leaves message as the RPC text, computes echoText separately from the media content type, persists that echo key, and returns it alongside sentText. (extensions/imessage/src/send.ts:228, 5ad977655381)
  • PR head routes caches to the internal echo key: The updated delivery path records sent.echoText before falling back to sent.sentText, preserving media-only echo matching while keeping the RPC text empty. (extensions/imessage/src/monitor/deliver.ts:62, 5ad977655381)
  • PR tests assert the intended split: The PR adds coverage that a media-only image send calls the RPC with text: "", returns sentText: "", and keeps echoText: "<media:image>"; it also preserves literal placeholder text when no attachment is sent. (extensions/imessage/src/send.test.ts:73, 5ad977655381)

Likely related people:

  • @omarshahine: Path history ties the private-API iMessage send path to e259751ec9c9ed10b292ce3e0b987c4b18b19a0c, and the PR discussion says they pushed the echo-cache follow-up at 5ad977655381486ec0fa13b143ca4dcc382d87e6. (role: feature owner and recent follow-up author; confidence: high; commits: e259751ec9c9, 5ad977655381; files: extensions/imessage/src/send.ts, extensions/imessage/src/monitor/deliver.ts)
  • @steipete: GitHub path history shows several recent iMessage send and delivery refactors, including 827b0de0ce7477c80f9b481dc835229f16a9c17d, 05eda57b3c72e61d31a07c38df2edb3ef0843c62, and 235d06bff13d3ec2bca9cf46cebee423f4876926. (role: recent adjacent area contributor; confidence: high; commits: 827b0de0ce74, 05eda57b3c72, 235d06bff13d; files: extensions/imessage/src/send.ts, extensions/imessage/src/monitor/deliver.ts)
  • @rmarr: Commit b29e180ef407c8e19407284ae8162f739f2ecafa changed iMessage echo-cache matching and post-send cache behavior, which is the compatibility risk this PR had to preserve. (role: echo-cache behavior contributor; confidence: medium; commits: b29e180ef407; files: extensions/imessage/src/monitor/echo-cache.ts, extensions/imessage/src/monitor/deliver.ts)

Remaining risk / open question:

  • The PR still lacks live after-fix proof showing a real native iMessage media send no longer displays the placeholder in Messages.app.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 1d6e5f7a3e42.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 12, 2026
@omarshahine

Copy link
Copy Markdown
Contributor

Independent reproduction on v2026.5.12-beta.3

Confirmed the bug on a separate machine running OpenClaw v2026.5.12-beta.3 (cc46ca9), native iMessage path (imsg private-API).

Synthetic repro — same recipe as the PR description, run from ~/GitHub/openclaw/ against the unpatched extensions/imessage/src/send.ts:

{
  "sentText": "<media:image>",
  "rpcMethod": "send",
  "rpcText": "<media:image>",
  "rpcFile": "/tmp/proof-image.png",
  "receiptKind": "media"
}

Line 198 of send.ts calls resolveDeliveredIMessageText, which (line 105) returns "<media:image>" whenever the outbound text is empty and the resolved attachment is an image — exactly as the PR describes.

Live reproopenclaw message send --channel imessage --target imessage:+1XXXXXXXXXX --media ./image.png delivered the image to Messages.app on the recipient side with a visible <media:image> text bubble adjacent to the image preview, matching Rich's original report.

Fix LGTM — agrees with the synthetic before/after.

@omarshahine

Copy link
Copy Markdown
Contributor

@homer-byte thanks for the focused fix. The visible iMessage placeholder bug is real, but I think this still needs one adjustment before merge.

Finding: preserve media echo matching while clearing RPC text

The PR correctly changes the outbound RPC payload for media-only sends to use text: "", which should stop the recipient-visible <media:image> bubble. But it also removes the only media-only echo key that the iMessage monitor uses to dedupe the agent's own sent media.

Current main uses the synthesized <media:image> text in two internal places:

  • extensions/imessage/src/send.ts: the sent text is persisted via rememberPersistedIMessageEcho(...)
  • extensions/imessage/src/monitor/deliver.ts: sent.sentText is recorded in the in-memory sentMessageCache

The inbound monitor still has explicit coverage for attachment-only echo fallback by bodyText placeholder in extensions/imessage/src/monitor/inbound-processing.test.ts. That matters when the send result is ok/unknown, or when the inbound row has a different ID shape than the outbound result. In those cases, the text/media echo key is what prevents OpenClaw from re-ingesting its own media echo.

Best fix: keep RPC text: "" and keep sentText/receipt behavior non-visible, but preserve a separate internal echo key, for example echoText: "<media:image>", and have the persisted/in-memory echo caches use that key for media-only sends. Please add regression coverage for the id-missing or id-mismatched media echo case so the visible-placeholder fix does not reintroduce duplicate self-echo replies.

@omarshahine omarshahine self-requested a review May 13, 2026 05:08
@omarshahine

Copy link
Copy Markdown
Contributor

Pushed a maintainer follow-up in f8a9877 to address the echo-cache blocker.

What changed:

  • Keeps the outbound imsg RPC text empty for media-only sends, so the recipient does not see <media:image>.
  • Adds an internal echoText key on the iMessage send result.
  • Uses echoText for persisted and in-memory sent-message echo caches, preserving <media:image> matching for id-missing or id-mismatched media echoes.
  • Adds focused coverage in send.test.ts and monitor/deliver.test.ts.
  • Adds a changelog entry for fix(imessage): avoid visible media placeholder text #81209.

Verification:

  • pnpm test extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm exec oxlint extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm test extensions/imessage
  • pnpm check:changed

@clawsweeper re-review

@omarshahine omarshahine force-pushed the fix/imessage-media-only-no-placeholder branch from f8a9877 to 5ad9776 Compare May 13, 2026 15:47
@omarshahine

Copy link
Copy Markdown
Contributor

Rebased the maintainer follow-up onto current main after #78580 landed.

New head: 5ad9776.

Post-rebase verification:

  • pnpm test extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm exec oxlint extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm check:changed

@clawsweeper re-review

@omarshahine

Copy link
Copy Markdown
Contributor

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
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.

Re-review progress:

@omarshahine omarshahine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved from maintainer review after the follow-up at 5ad9776.

The visible iMessage bug is reproduced on v2026.5.12-beta.3, and the maintainer patch now keeps RPC text: "" for media-only sends while preserving a separate internal echoText for persisted and in-memory echo matching. That resolves the previous blocker without reintroducing visible <media:image> delivery.

Local verification on the rebased head:

  • pnpm test extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm exec oxlint extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts
  • pnpm test extensions/imessage
  • pnpm check:changed

@omarshahine omarshahine force-pushed the fix/imessage-media-only-no-placeholder branch from 5ad9776 to 463afd4 Compare May 13, 2026 16:02
@omarshahine omarshahine merged commit c3e5d85 into openclaw:main May 13, 2026
17 checks passed
@omarshahine

Copy link
Copy Markdown
Contributor

Landed via squash merge after maintainer follow-up and rebase onto current main.

  • Source head: 463afd4
  • Merge commit: c3e5d85
  • Local verification on the maintainer patch: pnpm test extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.test.ts, pnpm exec oxfmt --check --threads=1 CHANGELOG.md extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts, pnpm exec oxlint extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts extensions/imessage/src/monitor/deliver.ts extensions/imessage/src/monitor/deliver.test.ts, pnpm test extensions/imessage, and pnpm check:changed.
  • Merge basis: maintainer approval on the echo-key fix plus the supplied live v2026.5.12-beta.3 repro proof; GitHub CI for the rebased head was queued with no required-check gate reported.

Thanks @homer-byte.

@clawsweeper

clawsweeper Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
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.

Re-review progress:

steipete pushed a commit that referenced this pull request May 13, 2026
Keep media-only iMessage sends from delivering visible <media:image> text while preserving a non-visible echo key for self-echo dedupe. Thanks @homer-byte.

(cherry picked from commit c3e5d85)
l3ocifer pushed a commit to l3ocifer/frack-openclaw that referenced this pull request May 13, 2026
Keep media-only iMessage sends from delivering visible <media:image> text while preserving a non-visible echo key for self-echo dedupe. Thanks @homer-byte.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Keep media-only iMessage sends from delivering visible <media:image> text while preserving a non-visible echo key for self-echo dedupe. Thanks @homer-byte.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Keep media-only iMessage sends from delivering visible <media:image> text while preserving a non-visible echo key for self-echo dedupe. Thanks @homer-byte.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Keep media-only iMessage sends from delivering visible <media:image> text while preserving a non-visible echo key for self-echo dedupe. Thanks @homer-byte.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: imessage Channel integration: imessage proof: supplied External PR includes structured after-fix real behavior proof. size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants