Skip to content

fix(message-tool): hydrate attachments[] on reply (and other single-attachment actions)#86724

Closed
omarshahine wants to merge 2 commits into
mainfrom
fix/reply-attachments-array-hydration
Closed

fix(message-tool): hydrate attachments[] on reply (and other single-attachment actions)#86724
omarshahine wants to merge 2 commits into
mainfrom
fix/reply-attachments-array-hydration

Conversation

@omarshahine

Copy link
Copy Markdown
Contributor

Summary

Agents that followed the published message tool schema and passed attachments: [{ path, mimeType, name }] on action: "reply" had the file silently dropped — only the text portion shipped. No error was raised. Same gap on sendAttachment, setGroupIcon, and upload-file.

Root cause

  • src/agents/tools/message-tool.ts:194 declares attachments[] as a top-level peer of media|mediaUrl|path|filePath|buffer etc., with path|filePath|media|mediaUrl|fileUrl|url|mimeType|name per-entry. No action scoping.
  • src/infra/outbound/message-action-runner.ts:506 (collectMessageAttachmentMediaHints) reads attachments[] — but it's only called from buildSendPayloadParts, which is only reached on action: "send" and the webchat internal-source-reply sink. Every other action path never sees attachments[].
  • src/infra/outbound/message-action-params.ts:353 (hydrateAttachmentActionPayload), used by reply / sendAttachment / setGroupIcon / upload-file, reads only top-level media|mediaUrl|path|filePath|fileUrl for hints. args.buffer never gets populated.
  • Each channel's reply handler then sees no hydrated buffer and no top-level path. E.g. extensions/imessage/src/actions.ts:290 extractReplyAttachment returns null (only throws on the bypass case — a top-level path without a buffer — not on absence), so the reply ships text-only. Silent drop.

Affects every channel that supports reply with media — iMessage, Telegram, WhatsApp, Discord, BlueBubbles, etc. — because they all share the same hydration step.

Fix

Lift the first usable attachments[] entry into the top-level hint slots inside hydrateAttachmentActionPayload before reading them:

  • path|filePath|media|mediaUrl|fileUrl|urlargs.path (if no top-level hint and no buffer already)
  • mimeType|contentTypeargs.contentType (canonical; hydration falls back to mimeType too)
  • name|filenameargs.filename

Backwards-compatible: only fills empty slots, explicit top-level hints win. The lifted path still flows through loadWebMedia with the resolved mediaPolicy, so all sandbox / mediaLocalRoots / size enforcement is preserved. No security boundary changes.

How I found it

Real-world repro: I (the lobster gateway, a personal agent on a Mac running openclaw 2026.5.26) was asked to "send me an image of a red coffee mug on a blue table." I wrote an SVG via apply_patch, then invoked message(action: "reply", attachments: [{path: ".../tmp-red-mug-blue-table.svg", mimeType: "image/svg+xml", name: "..."}]). The owner replied "Nothing was sent." I retried with a real PNG path in attachments[] — same result. imsg history --chat-id <dm> --attachments confirmed the text portion landed both times but no attachment row was attached. Tracing it back from imessageMessageActions.handleAction showed the iMessage handler is fine (it gets called with no buffer and no path-bypass keys; extractReplyAttachment correctly returns null). The hydration step never saw attachments[].

Test plan

  • Added regression tests in src/infra/outbound/message-action-runner.media.test.ts for the reply action:
    • hydrates buffer/filename/contentType from attachments[] on reply
    • prefers an explicit top-level hint over attachments[] entries
  • All 26 tests in message-action-runner.media.test.ts pass (24 pre-existing + 2 new).
  • Full src/infra/outbound suite: pre-existing failure count drops from 119 → 107 with this patch (no new failures introduced; the lift also clears 12 pre-existing failures where the same shape was tested elsewhere).

@openclaw-barnacle openclaw-barnacle Bot added channel: imessage Channel integration: imessage size: M maintainer Maintainer-authored PR labels May 26, 2026
@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed May 26, 2026, 2:58 AM ET / 06:58 UTC.

Summary
The PR lifts the first structured attachments[] entry into single-attachment action hydration and adds reply regression coverage.

PR surface: Source +53, Tests +51. Total +104 across 2 files.

Reproducibility: yes. Current source shows the schema accepts attachments[] while the single-attachment hydration path only reads top-level hints, and the PR discussion includes live pre/post iMessage evidence for the silent text-only drop.

Review metrics: 1 noteworthy metric.

  • Shared action surface: 4 single-attachment actions affected. The hydrator handles reply, sendAttachment, setGroupIcon, and upload-file, so source and metadata precedence affect multiple delivery paths before channel handlers run.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

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

Rank-up moves:

  • Fix the hydration ordering so attachments[] sources feed sandbox normalization and media-access root collection before loading.
  • Add regression coverage that explicit top-level media is not given metadata from an ignored attachment entry.

Mantis proof suggestion
A Telegram Desktop proof would materially exercise the sibling visible transport path for the shared reply attachment behavior. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

telegram desktop proof: after the hydration fix, verify that message(action:"reply", attachments:[{path,mimeType,name}]) sends the attachment visibly in a Telegram reply.

Risk before merge

  • Merging as-is can still reject attachments[] local paths that the equivalent top-level path would allow, because the attachment source is lifted after media-access root collection.
  • Merging as-is can send the explicit top-level media buffer with MIME type or filename copied from an ignored attachments[] entry.

Maintainer options:

  1. Fix source selection before merge (recommended)
    Move or mirror the attachment-source lift so attachments[] is included before sandbox normalization/media-access collection, and only copy metadata from the selected source.
  2. Accept a narrower iMessage fix
    Maintainers could land the current default-root iMessage improvement while knowingly leaving host-root expansion and explicit-source metadata follow-up work to a separate PR.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Fix the attachment lift so attachments[] sources participate in the same sandbox normalization and mediaAccess local-root collection as top-level path/media hints before hydration; only copy attachment mime/name when that attachment source is selected, and add focused regression tests for explicit top-level media with ignored attachment metadata and host-local attachment root expansion.

Next step before merge
A narrow automated repair can address the two deterministic hydration-order and metadata-precedence bugs before maintainer review.

Security
Cleared: The diff adds no dependencies, scripts, or secret handling changes, and the remaining issues are functional delivery/metadata bugs rather than a concrete security regression.

Review findings

  • [P2] Lift attachment sources before resolving media access — src/infra/outbound/message-action-params.ts:413
  • [P2] Only copy metadata from the selected attachment — src/infra/outbound/message-action-params.ts:157-162
Review details

Best possible solution:

Keep the shared hydrator approach, but choose attachment source and metadata atomically and make attachments[] participate in the same sandbox normalization and media-access source collection as top-level media hints.

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

Yes. Current source shows the schema accepts attachments[] while the single-attachment hydration path only reads top-level hints, and the PR discussion includes live pre/post iMessage evidence for the silent text-only drop.

Is this the best way to solve the issue?

No. The shared hydrator is the right boundary, but this patch needs a narrower correction so source selection, metadata, sandbox normalization, and host media root collection remain consistent.

Full review comments:

  • [P2] Lift attachment sources before resolving media access — src/infra/outbound/message-action-params.ts:413
    This lift runs inside hydration, but runMessageAction has already normalized sandbox media params and computed mediaAccess.localRoots from collectActionMediaSourceHints before calling it. A local file that works as a top-level path via root expansion can still be rejected when supplied in attachments[]; include structured attachments in source collection or lift before normalization/access resolution.
    Confidence: 0.84
  • [P2] Only copy metadata from the selected attachment — src/infra/outbound/message-action-params.ts:157-162
    When a caller supplies explicit top-level media/path plus an attachments[] entry, this helper leaves the explicit source in place but still copies the ignored attachment's mimeType and name into contentType/filename. Hydration can then send the explicit media buffer with stale MIME/name metadata, so copy metadata only from the source that is actually lifted.
    Confidence: 0.87

Overall correctness: patch is incorrect
Overall confidence: 0.84

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 3b023e9bdb62.

Label changes

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (logs): The PR discussion includes after-fix live Mac iMessage logs and a screenshot showing a reply call with attachments[] delivering an attachment row instead of text-only delivery.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P1: The PR addresses a real silent attachment drop in agent/channel replies and the remaining bugs can still affect visible message delivery.
  • merge-risk: 🚨 compatibility: The patch changes model-facing message-tool argument semantics across shared single-attachment actions, including precedence between explicit top-level hints and structured attachments.
  • merge-risk: 🚨 message-delivery: Incorrect source collection or stale MIME/name metadata can cause attachments to be rejected, mislabeled, or delivered incorrectly across channels.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (logs): The PR discussion includes after-fix live Mac iMessage logs and a screenshot showing a reply call with attachments[] delivering an attachment row instead of text-only delivery.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR discussion includes after-fix live Mac iMessage logs and a screenshot showing a reply call with attachments[] delivering an attachment row instead of text-only delivery.
  • mantis: telegram-visible-proof: Mantis should capture Telegram visible proof. The shared message-tool reply attachment behavior is visible in Telegram chat delivery and can be demonstrated with a short Telegram Desktop proof after the code fixes land.
Evidence reviewed

PR surface:

Source +53, Tests +51. Total +104 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 53 0 +53
Tests 1 51 0 +51
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 104 0 +104

Acceptance criteria:

  • node scripts/run-vitest.mjs src/infra/outbound/message-action-runner.media.test.ts
  • node scripts/run-vitest.mjs src/infra/outbound/message-action-params.test.ts
  • git diff --check

What I checked:

  • Current schema advertises structured attachments: Current main exposes attachments[] in the message tool schema with path/media/url and mime/name fields, without scoping that shape only to send. (src/agents/tools/message-tool.ts:194, 3b023e9bdb62)
  • Current single-attachment hydration misses attachments[]: Current main's hydrateAttachmentActionPayload reads only top-level media/file/content-type hints, so structured attachments[] is not consumed on reply/single-attachment actions. (src/infra/outbound/message-action-params.ts:364, 3b023e9bdb62)
  • PR lift runs after media access is resolved: The runner computes sandbox normalization and agent-scoped mediaAccess.localRoots from top-level media hints before calling hydrateAttachmentParamsForAction, so a lift inside the hydrator is too late for host-root expansion. (src/infra/outbound/message-action-runner.ts:1365, 3b023e9bdb62)
  • PR copies metadata from an ignored attachment: The PR only conditionally copies the attachment source, but still copies mimeType and name from that attachment into top-level contentType and filename, even when an explicit top-level media/path source wins. (src/infra/outbound/message-action-params.ts:157, 01e7b3cdef5d)
  • Real proof exists for the intended iMessage path: The PR discussion includes live Mac iMessage before/after logs showing pre-patch text-only delivery for reply with attachments[] and post-patch delivery with an attachment row, plus a screenshot. (01e7b3cdef5d)
  • Feature history points to shared outbound media owners: Path commit history shows prior reply-attachment work in 200eb62ef4ce6c94880cb717ee8d1dfe4e5c98c9, recent upload-file media work in 29f39db8578c521d7d19a9acbe3ff9b02121928b, and recent message-tool/schema work in 5dc704361f6f0fa54c1fcb374aa79a68e30c5f2b. (src/infra/outbound/message-action-params.ts:353, 200eb62ef4ce)

Likely related people:

  • omarshahine: Previously authored the merged reply-attachment wiring work on the same shared hydration path and supplied the current live reproduction/proof. (role: prior reply-attachment contributor; confidence: high; commits: 200eb62ef4ce; files: src/infra/outbound/message-action-params.ts, src/infra/outbound/message-action-runner.media.test.ts)
  • ngutman: Recent merged upload-file/media work touched message-action-params.ts, which is the shared single-attachment hydration surface under review. (role: recent shared hydrator contributor; confidence: medium; commits: 29f39db8578c; files: src/infra/outbound/message-action-params.ts)
  • steipete: Recent commits touched the message-tool schema/coercion and shared media path infrastructure adjacent to this PR's source and compatibility boundary. (role: recent message-tool and media-path contributor; confidence: medium; commits: 5dc704361f6f, 77d9ac30bb8d, 538605ff44d2; files: src/agents/tools/message-tool.ts, src/infra/outbound/message-action-params.ts, src/media/web-media.ts)
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 rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 26, 2026
@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🔥 Warming up: real-behavior proof passed; findings, security review, or rank-up moves are still in progress.

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.
What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@omarshahine

Copy link
Copy Markdown
Contributor Author

End-to-end repro evidence on a live Mac iMessage gateway

Captured from a live agent session running this branch (openclaw 2026.5.26 (6e66c30)). The user (the gateway's owner) sent send me a small test image to the agent's DM. The model invoked the message tool's reply action with the documented attachments[] shape. Trimmed from ~/.openclaw/agents/<agent>/sessions/<id>.jsonl:

Patched runner: attachments[] reaches the iMessage handler

// 1) Model -> message tool: structured attachments[] (exactly the shape that used to be silently dropped pre-patch)
{
  "role": "assistant",
  "content": [{
    "type": "toolCall",
    "name": "message",
    "arguments": {
      "action": "reply",
      "target": "***",
      "messageId": "323",
      "message": "🦞 Sending a small test image now.",
      "attachments": [{
        "path": "/Users/.../tmp-test-image.png",
        "type": "image",
        "mimeType": "image/png",
        "name": "tmp-test-image.png"
      }]
    }
  }]
}

// 2) Tool result — isError:true, surfaced from the iMessage channel's send-rich path
{
  "role": "toolResult",
  "isError": true,
  "content": "Dylib error: Failed to create attachment dir: You don't have permission to save the file \"any;-;***\" in the folder \"09\"."
}

The isError:true result is a separate problem (turned out to be a stale dev-build of the imsg cliPath bridge whose attachment-staging dir layout was outdated; switching to the current brew imsg release fixed the dylib side). The point for this PR: the patched runner correctly hydrated attachments[], loaded the buffer, routed it through the iMessage reply handler, and surfaced a real failure.

Pre-patch behavior for the same input

Without this patch the same attachments:[{...}] call returned {ok:true} from the tool and the reply shipped text-only with no error. From the same session, two earlier turns that ran against unpatched code:

[sent] +1***: 🦞 Here you go.         ← text only, no attachment row
[sent] +1***: 🦞 You're right — retrying as a PNG.   ← still text only
[recv] +1***: Did not get anything

extractReplyAttachment returned null because attachments[] was never consumed; args.buffer stayed empty; the iMessage handler's "loud rejection" branch only fires when a top-level path is present without a buffer, so absence-of-everything → silent text-only delivery.

Surface

Affects every channel that supports reply with media (iMessage, Telegram, WhatsApp, Discord, BlueBubbles, …) — they all share hydrateAttachmentActionPayload in src/infra/outbound/message-action-params.ts, which was reading only top-level hint slots. The lift makes the documented attachments[] shape work uniformly.

@omarshahine

omarshahine commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

End-to-end verification: patched code path delivers a real iMessage attachment

Captured on a live Mac iMessage gateway running this branch (openclaw 2026.5.26 (6e66c30)). The owner DM'd the agent the same request twice — once to the direct chat, once to a group — and the agent's reply landed both times with the attached PNG.

DM (chat_id=3) — uses the patched attachments[] code path

User prompt:

[recv] Omar: send me a any image from /Users/lobster/.openclaw/media/tool-image-generation/

Model's tool call (from session JSONL, trimmed):

{
  "name": "message",
  "arguments": {
    "action": "reply",
    "messageId": "327",
    "message": "🦞 Sending one from the generated-media folder.",
    "attachments": [{
      "path": "/Users/.../media/tool-image-generation/red-coffee-mug-blue-table---a46a540c-...png",
      "type": "image",
      "mimeType": "image/png",
      "name": "red-coffee-mug-blue-table.png"
    }]
  }
}

Tool result: {"ok": true, "messageId": "...", "repliedTo": "..."}

imsg history --chat-id 3 confirms the attachment row:

2026-05-26T03:00:57Z [sent] +1***: 🦞 Sending one from the generated-media folder.
  attachment: name=upload.png mime=image/png  path=Attachments/imsg/D7612E75-3FD4-45E1-97BB-A6D661D6F35B/upload.png

This is the exact shape that pre-patch returned {ok:true} with text-only delivery and no attachment row. Post-patch the runner lifts attachments[0] into the top-level hint, hydration loads the buffer, the iMessage handler calls sendRichMessage(... attachment: ...), and the file ships.

Group (chat_id=22) — confirms broader iMessage attachment delivery still healthy

Same prompt, group chat. Model chose the long-supported top-level media: field here (not the patched attachments[] shape), and that path also delivered as expected — this confirms no regression in the existing media flow:

[recv] Omar (in group): send me a any image from .../tool-image-generation/
[sent] lobster: 🦞 test image
  attachment: name=upload.png  path=Attachments/imsg/D9CA5A96-3E46-4F83-BD42-BE4AE939A357/upload.png
image

Summary

  • DM proves the fix: message(action:"reply", attachments:[{...}]) now delivers an iMessage attachment instead of a silent text-only drop. ✅
  • Group proves no collateral damage to the pre-existing top-level media: path. ✅
  • 26/26 tests in src/infra/outbound/message-action-runner.media.test.ts pass. ✅
  • Broader src/infra/outbound suite: 12 fewer failures with this patch vs base, no new failures introduced.

Receiving end (Omar): "first one worked, DM now group" → both confirmed visually on the iPhone.

@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 26, 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:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 26, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper what happened to our re-review?

@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper assist is taking a look at your question.

I queued a lightweight read-only assist pass. It will post a separate answer comment and will not edit the durable ClawSweeper review comment or trigger close, merge, repair, label, or branch changes.

Request: what happened to our re-review?

@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper assist: the re-review appears to have run and updated the existing ClawSweeper review comment, but it did not produce a clean approval. The current durable review still says “needs changes before merge” for head SHA 9e49fa5f46da6685a1a023c879639d1be83b9b57.

Evidence:

  • Previous ClawSweeper review comment: fix(message-tool): hydrate attachments[] on reply (and other single-attachment actions) #86724 (comment)
  • That comment was updated at 2026-05-26T03:28:50Z, after Omar added live proof at 03:02:54Z.
  • The PR labels changed at 03:28:46Z-03:28:49Z: proof: sufficient was added, status: 📣 needs proof was removed, and status: ⏳ waiting on author was added.
  • The review is still tied to the current head SHA 9e49fa5f46da6685a1a023c879639d1be83b9b57.
  • The remaining review findings shown in context are P1s around the reverted iMessage detour / failed imsg JSON replies, not proof sufficiency.

Suggested next action: ask for a full fresh correctness pass with @clawsweeper review if you want the current head re-reviewed from scratch.


Source: #86724 (comment)
Assist model: gpt-5.5, reasoning low.

@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper review

@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label May 26, 2026
…roupIcon/upload-file

The message-tool schema (src/agents/tools/message-tool.ts:194) advertises a
structured `attachments: [{path, mimeType, name, …}]` array as a peer of the
top-level `media|mediaUrl|path|filePath` slots. The `send` action consumed it
via `collectMessageAttachmentMediaHints` in the runner, but the single-attachment
hydration path used by `reply`, `sendAttachment`, `setGroupIcon`, and
`upload-file` (`hydrateAttachmentActionPayload` in message-action-params.ts) only
looked at top-level keys. Agents that followed the published schema and passed
`attachments: [{path: "/abs/pic.png", mimeType: "image/png", name: "pic.png"}]`
to `action: "reply"` had hydration find no hint, no buffer was loaded, and the
channel handler shipped text-only with the attachment silently dropped. No
error was raised because `extractReplyAttachment` only throws on a *bypass*
shape (top-level path with no buffer), not on absence — and `attachments[]`
left every top-level slot empty.

Fix: lift the first attachment's path / mimeType / filename into the
top-level slots inside `hydrateAttachmentActionPayload` before reading hints.
Backward-compatible (only fills empty slots; explicit top-level hints win) and
preserves all existing sandbox / localRoots / size enforcement, since the
loaded `mediaSource` still flows through `loadWebMedia` with the resolved
policy.

Bug surface: every channel's `reply` action (iMessage, Telegram, WhatsApp,
Discord, BlueBubbles, …), since they all share the same core hydration step.
@omarshahine omarshahine requested a review from steipete May 26, 2026 06:16
@openclaw-barnacle openclaw-barnacle Bot added size: S and removed channel: imessage Channel integration: imessage size: M labels May 26, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the prior P1 scope finding on head 6a2cb8c941.

Changes:

Verification:

  • pnpm format:check -- src/infra/outbound/message-action-params.ts src/infra/outbound/message-action-runner.media.test.ts extensions/imessage/src/send.ts extensions/imessage/src/send.test.ts
  • node scripts/run-vitest.mjs src/infra/outbound/message-action-runner.media.test.ts - 1 file, 26 tests passed
  • git diff --check

@clawsweeper

clawsweeper Bot commented May 26, 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 force-pushed the fix/reply-attachments-array-hydration branch from 6a2cb8c to 01e7b3c Compare May 26, 2026 06:28
@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 rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels May 26, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Rebased onto current origin/main (c04c03f8e9) — branch now contains only the hydration lift (01e7b3cdef). The earlier base commit 6e10d32a37 (reverted detour) has been dropped, so extensions/imessage/src/send.ts is untouched. Diff is now 2 files / +104 lines: src/infra/outbound/message-action-params.ts (lift function) and src/infra/outbound/message-action-runner.media.test.ts (regression tests). Should resolve the two P1 findings about reintroducing the iMessage detour and the imsg JSON success handling.

@clawsweeper

clawsweeper Bot commented May 26, 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:

@clawsweeper clawsweeper Bot added 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. and removed 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. labels May 26, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the two P2 findings on head f079dbb69c.

Changes:

  • Lift the first structured attachments[] source before sandbox media normalization and before agent-scoped media access/local-root expansion, so host-local attachment paths go through the same policy path as top-level media/path.
  • Preserve explicit top-level media/path/buffer inputs. If one is already present, the ignored attachments[] entry no longer contributes contentType or filename metadata.
  • Added regressions for host-local attachments[] root expansion and for explicit top-level media not inheriting ignored attachment metadata.

Verification:

  • node_modules/.bin/oxfmt --check --threads=1 src/infra/outbound/message-action-params.ts src/infra/outbound/message-action-runner.ts src/infra/outbound/message-action-runner.media.test.ts
  • node scripts/run-vitest.mjs src/infra/outbound/message-action-runner.media.test.ts src/infra/outbound/message-action-params.test.ts (47 passed)
  • pnpm tsgo
  • git diff --check
  • .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main -> clean, no accepted/actionable findings

Known proof gap: this is still a unit/integration proof for the shared outbound runner path. No live channel proof was added for this shared-core metadata/root-expansion fix.

@clawsweeper

clawsweeper Bot commented May 26, 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

Copy link
Copy Markdown
Contributor Author

@steipete you were right to call this out. I fixed the shared outbound issues instead of trying to explain them away.

What changed on f079dbb69c:

  • attachments[] sources are now lifted before sandbox normalization and media-access/local-root resolution, so a host-local file in attachments[] gets the same policy checks and root expansion as a top-level media/path.
  • If the caller already supplied explicit top-level media/path/buffer, we leave attachments[] ignored entirely, including its mimeType/name. That prevents stale metadata from an ignored attachment from being applied to the actual media.
  • Added focused regressions for both cases.

Why we missed it: the earlier pass was focused on the user-visible iMessage attachment symptom and the first ClawSweeper scope finding. I had run focused tests and ClawSweeper review, but I had not run the repo’s Codex autoreview closeout on the corrected branch before asking for another look. That was the process miss; the root policy documents this under AGENTS.md pre-land/pre-commit review rules, and the concrete workflow is in .agents/skills/autoreview/SKILL.md.

Process change for this PR and future nontrivial code PRs: before pushing/landing, run .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main, fix accepted/actionable findings, rerun focused tests if code changes, then report the clean autoreview result in the PR. I did that here after the fix; Codex autoreview is now clean on head f079dbb69c.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels May 26, 2026
@omarshahine

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #86870 (Peter Steinberger, merged 2026-05-26T13:50:33Z). His fix lands the same conceptual change with a cleaner abstraction:

  • Introduces typed StructuredAttachmentSource + collectStructuredAttachmentSources() + resolveStructuredAttachmentSource() + hasExplicitAttachmentPayload() instead of mutating args in place.
  • Threads structured-source info through collectActionMediaSourceHints and hydrateAttachmentActionPayload so single-attachment actions select structured attachments only when no top-level or plugin source wins, and send collects all of them.
  • Carries proof: typecheck, autoreview clean, hydration probe.

End-to-end iMessage delivery via patched runner was verified live during this PR's work (DM chat 3 at 03:00:57Z + group chat 22 at 03:01:18Z, both upload.png rows in imsg history). The bug is dead either way.

@omarshahine omarshahine deleted the fix/reply-attachments-array-hydration branch May 31, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant