Skip to content

Fix embedded replies dropping structured tool-result media#54892

Closed
rengers wants to merge 16 commits intoopenclaw:mainfrom
rengers:fix/telegram-tool-media-forwarding
Closed

Fix embedded replies dropping structured tool-result media#54892
rengers wants to merge 16 commits intoopenclaw:mainfrom
rengers:fix/telegram-tool-media-forwarding

Conversation

@rengers
Copy link
Copy Markdown

@rengers rengers commented Mar 26, 2026

Summary

  • Problem: Embedded replies only preserved media parsed from inline MEDIA: directives, and could drop structured tool-result media already present in details.media.mediaUrls.
  • Why it matters: Tool-generated attachments (for example from image_generate) could be lost before outbound delivery even though the tool result already contained valid media metadata.
  • What changed: Embedded tool-result emission now merges inline-parsed media with structured media extracted from the tool result before calling onToolResult(...).
  • What did NOT change (scope boundary): This does not change channel delivery code, media storage paths, or trust policy for tool-result media.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

Root Cause / Regression History (if applicable)

  • Root cause: The embedded reply path only forwarded media found in reply text and ignored structured media already present on the tool result object.
  • Missing detection / guardrail: There was helper coverage for media extraction, but no regression test that checked structured media survives through embedded reply emission.
  • Prior context (git blame, prior PR, issue, or refactor if known): Related to Telegram/local-media delivery debugging around generated images.
  • Why this regressed now: Inline MEDIA: handling and structured tool-result media handling existed side by side, but only the inline path was wired into this reply seam.
  • If unknown, what was ruled out: Ruled out image generation failure and missing local files; the drop happened in embedded reply emission before channel delivery.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
  • Unit test
  • Seam / integration test
  • End-to-end test
  • Existing coverage already sufficient
  • Target test or file: src/agents/pi-embedded-subscribe.tools.media.test.ts
  • Scenario the test should lock in: A tool result with details.media.mediaUrls and no inline MEDIA: directive still emits an onToolResult(...) payload containing the media URL.
  • Why this is the smallest reliable guardrail: The bug is in embedded reply construction, before channel-specific delivery.
  • Existing test that already covers this (if any): Existing tests covered media extraction helpers, but not propagation through subscribeEmbeddedPiSession(...).
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Trusted tool-generated media is now preserved in embedded reply flows without requiring manual inline MEDIA: reconstruction.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: OpenClaw 2026.3.24
  • Model/provider: OpenAI (gpt-5.4, gpt-image-1)
  • Integration/channel (if any): Telegram
  • Relevant config (redacted): embedded agent reply flow with native image_generate

Steps

  1. Run a trusted tool that returns structured media in details.media.mediaUrls.
  2. Let the embedded reply path emit tool results via onToolResult(...).
  3. Observe whether the media survives when the reply text contains no inline MEDIA: directive.

Expected

  • Structured media URLs are preserved in the emitted tool-result payload.

Actual

  • Before this change, those URLs could be dropped unless they were also present as inline MEDIA: text.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: Reproduced the bug in a live Telegram/image-generation flow, traced the reply seam, applied the fix, and passed the focused regression test.
  • Edge cases checked: Structured media with no inline MEDIA: directive; duplicate URL de-duping; trust filtering still applied.
  • What you did not verify: Full manual cross-channel verification beyond the reproduction path.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert this commit.
  • Files/config to restore: src/agents/pi-embedded-subscribe.ts
  • Known bad symptoms reviewers should watch for: Duplicate media in tool-result replies or unexpected media forwarding from untrusted tool results.

Risks and Mitigations

  • Risk: Duplicate media URLs if both inline and structured media are present.
  • Mitigation: Media URLs are merged with Set(...) de-duplication.
  • Risk: Accidentally broadening local-media forwarding.
  • Mitigation: Structured media still goes through filterToolResultMediaUrls(...), so existing trust filtering remains in effect.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 26, 2026
@rengers rengers marked this pull request as ready for review March 26, 2026 14:02
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 172627a116

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return;
}
try {
void params.onToolResult({
text: cleanedText,
mediaUrls: filteredMediaUrls.length ? filteredMediaUrls : undefined,
mediaUrls: mergedMediaUrls.length ? mergedMediaUrls : undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent duplicate structured media delivery from tool results

By adding structuredMediaUrls into the onToolResult payload here, structured attachments are now emitted once in tool-output callbacks and again later from pending media state in verbose full runs. handleToolExecutionEnd still queues structured media via queuePendingToolMedia(...) (src/agents/pi-embedded-subscribe.handlers.tools.ts:315-326), and dispatch logic still forwards media-only tool results (src/auto-reply/reply/dispatch-from-config.ts:645-651), so channels that handle both tool-result and block-reply streams can receive the same media twice for a single tool execution.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 26, 2026

Greptile Summary

This PR fixes a bug where tool-generated structured media (in details.media.mediaUrls) could be silently dropped from embedded reply payloads when there was no accompanying inline MEDIA: directive in the tool result text. The fix merges inline-parsed URLs with structured media extracted via extractToolResultMediaArtifact, with Set-based deduplication and proper trust filtering applied to both sources independently.

A secondary tightening is included in handlers.tools.ts: the old hasStructuredMedia early-return path in verbose/full mode is removed, replacing it with an unconditional early return after emitToolOutput. This prevents the double-delivery scenario where both emitToolOutput (now carrying structured media) and queuePendingToolMedia would queue the same URLs.

Key changes:

  • emitToolResultMessage in pi-embedded-subscribe.ts now calls extractToolResultMediaArtifact and merges the result with inline-parsed URLs before forwarding to onToolResult.
  • audioAsVoice is gated on structuredUrlsInMerged (post-filter structured URL count > 0), preventing the flag from leaking onto pure-inline media URLs.
  • emitToolResultOutput in handlers.tools.ts now always returns after emitToolOutput in verbose/full mode, removing the double-queue path.
  • A focused regression test locks in the primary bug scenario (verboseLevel: "full", structured media only, no inline MEDIA: directive).
  • The handler-level test is updated to expect no pending media queuing in verbose/full mode.

Confidence Score: 5/5

Safe to merge — both previously flagged concerns are resolved and the new regression test locks in the fix.

The two issues surfaced in prior review rounds (double media delivery in verbose/full mode and audioAsVoice leaking onto non-audio inline URLs) are each directly addressed by targeted code changes with matching test updates. The primary bug fix is minimal, correct, and fully covered by a new regression test. Trust filtering remains unchanged. No new risky surface area is introduced.

No files require special attention.

Important Files Changed

Filename Overview
src/agents/pi-embedded-subscribe.ts Core fix: emitToolResultMessage now merges inline-parsed MEDIA: URLs with structured details.media.mediaUrls via extractToolResultMediaArtifact, with Set-based dedup and a post-filter structuredUrlsInMerged guard for audioAsVoice.
src/agents/pi-embedded-subscribe.handlers.tools.ts Removes the hasStructuredMedia short-circuit that previously fell through to queuePendingToolMedia when structured media was present in verbose/full mode — now always returns early after emitToolOutput, preventing the double-delivery path.
src/agents/pi-embedded-subscribe.tools.media.test.ts Adds a regression test for the primary bug: structured media in details.media.mediaUrls with no inline MEDIA: directive still arrives in onToolResult after the fix.
src/agents/pi-embedded-subscribe.handlers.tools.media.test.ts Rename and update of existing test: previously asserted pending media was queued in verbose/full mode; now correctly asserts it is NOT queued, matching the dedup fix in the handlers file.

Reviews (2): Last reviewed commit: "Merge branch 'main' into fix/telegram-to..." | Re-trigger Greptile

Comment on lines +353 to 368
const structuredArtifact = extractToolResultMediaArtifact(result);
const structuredMediaUrls = structuredArtifact
? filterToolResultMediaUrls(toolName, structuredArtifact.mediaUrls ?? [], result)
: [];
const mergedMediaUrls = Array.from(
new Set([...(filteredMediaUrls ?? []), ...structuredMediaUrls]),
);
if (!cleanedText && mergedMediaUrls.length === 0) {
return;
}
try {
void params.onToolResult({
text: cleanedText,
mediaUrls: filteredMediaUrls.length ? filteredMediaUrls : undefined,
mediaUrls: mergedMediaUrls.length ? mergedMediaUrls : undefined,
...(structuredArtifact?.audioAsVoice === true ? { audioAsVoice: true } : {}),
});
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.

P1 Potential double delivery of structured media when verboseLevel is "full"

When shouldEmitToolOutput() is true AND hasStructuredMedia is true (i.e. details.media is present), emitToolResultOutput in handlers.tools.ts calls ctx.emitToolOutput(...) (which now routes through the updated emitToolResultMessage including structured media via mergedMediaUrls) and then falls through to queuePendingToolMedia with the same structured URLs.

// handlers.tools.ts (unchanged)
if (ctx.shouldEmitToolOutput()) {
    const outputText = extractToolResultText(sanitizedResult);
    if (outputText) {
        ctx.emitToolOutput(toolName, meta, outputText, result); // structured media now included here
    }
    if (!hasStructuredMedia) {
        return;   // skipped when structured media IS present
    }
}
// falls through to:
queuePendingToolMedia(ctx, { mediaUrls, ... });   // same media queued again

onToolResult and onBlockReply are different callbacks, but if the outer caller routes both to the same channel (e.g. Telegram), the generated image could be delivered twice. Before this PR, structured media only reached queuePendingToolMedia; onToolResult never carried it. Now it can arrive through both paths simultaneously.

Could you confirm whether callers that wire up both onToolResult and onBlockReply/pending-block-reply to the same delivery channel are guarded against duplicates, or whether the queuePendingToolMedia path is intentionally left unchanged here? When shouldEmitToolOutput() is true and the result has details.media, structured media now flows through both emitToolResultMessage → onToolResult and queuePendingToolMedia → onBlockReply. Is there de-duplication at the channel-delivery layer, or does the outer caller always use only one of these two paths for a given flow?

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-subscribe.ts
Line: 353-368

Comment:
**Potential double delivery of structured media when verboseLevel is "full"**

When `shouldEmitToolOutput()` is true AND `hasStructuredMedia` is true (i.e. `details.media` is present), `emitToolResultOutput` in `handlers.tools.ts` calls `ctx.emitToolOutput(...)` (which now routes through the updated `emitToolResultMessage` including structured media via `mergedMediaUrls`) **and** then falls through to `queuePendingToolMedia` with the same structured URLs.

```
// handlers.tools.ts (unchanged)
if (ctx.shouldEmitToolOutput()) {
    const outputText = extractToolResultText(sanitizedResult);
    if (outputText) {
        ctx.emitToolOutput(toolName, meta, outputText, result); // structured media now included here
    }
    if (!hasStructuredMedia) {
        return;   // skipped when structured media IS present
    }
}
// falls through to:
queuePendingToolMedia(ctx, { mediaUrls, ... });   // same media queued again
```

`onToolResult` and `onBlockReply` are different callbacks, but if the outer caller routes both to the same channel (e.g. Telegram), the generated image could be delivered twice. Before this PR, structured media only reached `queuePendingToolMedia`; `onToolResult` never carried it. Now it can arrive through both paths simultaneously.

Could you confirm whether callers that wire up both `onToolResult` and `onBlockReply`/pending-block-reply to the same delivery channel are guarded against duplicates, or whether the `queuePendingToolMedia` path is intentionally left unchanged here? When shouldEmitToolOutput() is true and the result has details.media, structured media now flows through both emitToolResultMessage → onToolResult and queuePendingToolMedia → onBlockReply. Is there de-duplication at the channel-delivery layer, or does the outer caller always use only one of these two paths for a given flow?

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/agents/pi-embedded-subscribe.ts Outdated
return;
}
try {
void params.onToolResult({
text: cleanedText,
mediaUrls: filteredMediaUrls.length ? filteredMediaUrls : undefined,
mediaUrls: mergedMediaUrls.length ? mergedMediaUrls : undefined,
...(structuredArtifact?.audioAsVoice === true ? { audioAsVoice: true } : {}),
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.

P2 audioAsVoice flag can attach to non-audio media in a mixed-source edge case

structuredArtifact?.audioAsVoice === true is spread unconditionally based solely on the artifact, regardless of whether any of its URLs survived filterToolResultMediaUrls. If all structured-media URLs are filtered away (e.g. local path from an untrusted tool) but filteredMediaUrls (inline MEDIA:) provides surviving URLs, the audioAsVoice: true flag would be forwarded together with those non-audio inline media URLs.

In practice this is unlikely because audioAsVoice is only set by trusted tools (e.g. tts) that would also pass the local-path trust filter. But if this invariant ever breaks, the downstream channel could mishandle image URLs as voice audio.

A tighter guard would be to only set audioAsVoice when at least one URL came from the structured artifact:

const structuredUrlsInMerged = structuredMediaUrls.length > 0;
...(structuredUrlsInMerged && structuredArtifact?.audioAsVoice === true
  ? { audioAsVoice: true }
  : {}),
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-subscribe.ts
Line: 367

Comment:
**`audioAsVoice` flag can attach to non-audio media in a mixed-source edge case**

`structuredArtifact?.audioAsVoice === true` is spread unconditionally based solely on the artifact, regardless of whether any of its URLs survived `filterToolResultMediaUrls`. If all structured-media URLs are filtered away (e.g. local path from an untrusted tool) but `filteredMediaUrls` (inline `MEDIA:`) provides surviving URLs, the `audioAsVoice: true` flag would be forwarded together with those non-audio inline media URLs.

In practice this is unlikely because `audioAsVoice` is only set by trusted tools (e.g. `tts`) that would also pass the local-path trust filter. But if this invariant ever breaks, the downstream channel could mishandle image URLs as voice audio.

A tighter guard would be to only set `audioAsVoice` when at least one URL came from the structured artifact:

```ts
const structuredUrlsInMerged = structuredMediaUrls.length > 0;
...(structuredUrlsInMerged && structuredArtifact?.audioAsVoice === true
  ? { audioAsVoice: true }
  : {}),
```

How can I resolve this? If you propose a fix, please make it concise.

@rengers rengers force-pushed the fix/telegram-tool-media-forwarding branch from 172627a to 9dea9a9 Compare March 26, 2026 14:37
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9dea9a9faa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +306 to +309
if (hasStructuredMedia) {
return;
}
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep queuing structured media when tool output text is empty

In full verbose runs, this unconditional return exits before the fallback media queueing path, so structured attachments are dropped whenever a tool result has details.media but no text content blocks. ctx.emitToolOutput(...) is only called when extractToolResultText(...) yields text, so media-only results (for example image results produced without extraText, such as the canvas snapshot path) now reach neither onToolResult nor pendingToolMedia delivery, causing missing outbound media in embedded replies.

Useful? React with 👍 / 👎.

@rengers rengers marked this pull request as draft March 26, 2026 14:42
@rengers rengers marked this pull request as ready for review March 26, 2026 15:39
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d2d8580aa1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +297 to +299
// In verbose/full mode, emitToolOutput handles media delivery.
// Do not also queue pending media to avoid duplicate sends.
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve media-only tool results in full verbose mode

Returning unconditionally from the ctx.shouldEmitToolOutput() branch drops structured media whenever extractToolResultText(sanitizedResult) is empty. In that case ctx.emitToolOutput(...) is never called, and this early return skips the fallback extractToolResultMediaArtifact(...) + queuePendingToolMedia(...) path, so tools that return only details.media (no text blocks) lose attachments entirely in verbose full runs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c8908b6fce

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 295 to 299
ctx.emitToolOutput(toolName, meta, outputText, result);
}
if (!hasStructuredMedia) {
// In verbose/full mode, emitToolOutput handles media delivery.
// Do not also queue pending media to avoid duplicate sends.
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid returning before media queue when tool callback is absent

This early return drops structured tool media whenever shouldEmitToolOutput() is true and extractToolResultText(...) is non-empty but onToolResult is not wired. In that case ctx.emitToolOutput(...) is effectively a no-op (the subscribe layer bails when onToolResult is undefined), and because this branch returns before queuePendingToolMedia(...), media attachments are never forwarded via the block-reply path. This is a regression from the prior flow for consumers that run in verbose=full without an onToolResult handler.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0967450ff

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +353 to +356
const structuredArtifact = extractToolResultMediaArtifact(result);
const structuredMediaUrls = structuredArtifact
? filterToolResultMediaUrls(toolName, structuredArtifact.mediaUrls ?? [], result)
: [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Suppress structured media when forwarding failed tool output

This new merge path forwards details.media attachments even for failed tool runs in verbose=full: emitToolResultOutput invokes emitToolOutput(...) before the isToolError guard in src/agents/pi-embedded-subscribe.handlers.tools.ts, and this code now unconditionally adds structured artifact URLs to the onToolResult payload. In practice, a tool result that has both error text and details.media.mediaUrls will send media despite the existing error-path intent to avoid media delivery, which can leak or deliver stale/corrupt attachments.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6220000db3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +295 to +296
const outputResult = isToolError ? undefined : result;
ctx.emitToolOutput(toolName, meta, outputText, outputResult);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve MCP provenance when emitting failed tool output

When isToolError is true, this branch now calls emitToolOutput with outputResult set to undefined, which strips details.mcpServer/mcpTool provenance before media filtering runs in emitToolResultMessage. In verbose=full sessions with onToolResult enabled, an external/MCP tool error that includes a MEDIA:/tmp/... line in text can now be treated as a trusted core-tool local path and forwarded, whereas previously the same payload was filtered out as external. Fresh evidence in this commit is the new const outputResult = isToolError ? undefined : result; assignment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23d70f8a2c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +353 to +355
const structuredArtifact = extractToolResultMediaArtifact(result);
const structuredMediaUrls = structuredArtifact
? filterToolResultMediaUrls(toolName, structuredArtifact.mediaUrls ?? [], result)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent error tool output from re-attaching MEDIA paths

Fresh evidence in this commit is the new extractToolResultMediaArtifact(result) merge path in emitToolResultMessage: that helper also parses legacy MEDIA: lines from result.content, so stripping only details.media/details.path for isToolError no longer prevents media forwarding. In verbose=full, a failed trusted tool that returns text like MEDIA:/tmp/... can now emit a local attachment through onToolResult, which contradicts the error-path media suppression and can expose stale or sensitive files.

Useful? React with 👍 / 👎.

@steipete
Copy link
Copy Markdown
Contributor

Closing this as not reproducible on current main after Codex review.

Current main already preserves structured tool-result media for embedded replies by extracting details.media, queueing it as pending tool media, and flushing it into outbound block replies at turn end. The PR’s reported user-visible drop before outbound delivery does not match current behavior on a6aa151653ab478e9aa23e95c6446c41adc4fce1.

What I checked:

Review notes: reviewed against a6aa151653ab.

@steipete steipete closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants