Skip to content

fix: deliver tool result images (screenshots) to Telegram#15987

Closed
strelov1 wants to merge 1 commit intoopenclaw:mainfrom
strelov1:fix/tool-result-images-not-sent-to-telegram
Closed

fix: deliver tool result images (screenshots) to Telegram#15987
strelov1 wants to merge 1 commit intoopenclaw:mainfrom
strelov1:fix/tool-result-images-not-sent-to-telegram

Conversation

@strelov1
Copy link

@strelov1 strelov1 commented Feb 14, 2026

Summary

  • Extract image paths directly from tool result content blocks via extractToolResultMediaPaths(), bypassing broken MEDIA: text parsing
  • Emit media through onToolResult regardless of verbose level (was gated by verboseLevel === "full")
  • Allow media-only payloads through normalizeStreamingText() (was skipping when text was empty)

Closes #11735

Test plan

  • npx vitest run src/agents/pi-embedded-subscribe.tools — 2/2 passed
  • npx vitest run src/auto-reply/reply/agent-runner — 45/45 passed
  • Manual: ask bot to take a screenshot in Telegram DM, verify image appears

🤖 Generated with Claude Code

Greptile Overview

Greptile Summary

Fixed screenshot delivery to Telegram by bypassing broken MEDIA: text parsing and verbose level restrictions. Introduces extractToolResultMediaPaths() to directly extract image paths from tool result content blocks, decouples media emission from the verboseLevel === "full" gate in handleToolExecutionEnd(), and allows media-only payloads through normalizeStreamingText().

Key changes:

  • Tool result images (screenshots) now reach Telegram regardless of verbose level
  • Media emission uses structured content block extraction instead of fragile text parsing
  • Media-only messages (no text) are properly delivered

Minor suggestion:

  • Consider adding unit tests for extractToolResultMediaPaths (src/agents/pi-embedded-subscribe.tools.ts:121)

Confidence Score: 4/5

  • Safe to merge with minor test coverage suggestion
  • Changes correctly address the stated problem with a reasonable approach. Integration tests pass. Main suggestion is adding unit tests for the new extraction function, but this is not blocking.
  • No files require special attention

Last reviewed commit: 213a8e4

(4/5) You can add custom instructions or style guidelines for the agent here!

Tool results containing images (browser screenshots, canvas snapshots)
were silently dropped due to three independent bugs:

1. emitToolOutput() gated by verboseLevel === "full" — most users
   never hit this path
2. Tool output wrapped in code fence before MEDIA: parsing — tokens
   inside fences are skipped
3. normalizeStreamingText() skipped media-only payloads (no text → skip)

Fix by extracting image paths directly from tool result content blocks
via new extractToolResultMediaPaths(), emitting them through
onToolResult regardless of verbose level, and allowing media-only
payloads through normalizeStreamingText().

Closes openclaw#11735

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: XS labels Feb 14, 2026
@diffray-bot
Copy link

Changes Summary

This PR fixes a bug where tool result images (screenshots, canvas snapshots) were not being delivered to messaging platforms like Telegram. The fix extracts image paths directly from tool result content blocks and ensures media is emitted regardless of verbose level settings.

Type: bugfix

Components Affected: Agent tool result processing, Telegram/messaging channel media delivery, Streaming text normalization

Files Changed
File Summary Change Impact
...space/src/agents/pi-embedded-subscribe.tools.ts Added extractToolResultMediaPaths() function to extract image paths from tool result content blocks ✏️ 🟡
.../agents/pi-embedded-subscribe.handlers.tools.ts Added logic to emit media from tool results via onToolResult callback regardless of verbose level ✏️ 🔴
.../src/auto-reply/reply/agent-runner-execution.ts Modified normalizeStreamingText() to allow media-only payloads (no text) to pass through instead of being skipped ✏️ 🟡
Architecture Impact
  • Coupling: Adds direct media path extraction from tool result content blocks, bypassing the previous MEDIA: text parsing mechanism

Risk Areas: Media-only payloads now allowed through normalizeStreamingText - verify no regressions with empty text handling, Tool result media emission now bypasses verbose level check - ensure this doesn't cause unexpected media delivery in minimal verbosity modes, New extractToolResultMediaPaths logic assumes specific structure of tool result content blocks (type='image', details.path)

Suggestions
  • Add unit tests for extractToolResultMediaPaths() to cover edge cases (missing path, multiple images, malformed content)
  • Verify behavior when tool results have images but no details.path field
  • Test interaction between media-only payloads and other payload processing logic downstream
  • Consider adding telemetry/logging to track successful media extraction for debugging

Full review in progress... | Powered by diffray

Comment on lines +121 to +142
export function extractToolResultMediaPaths(result: unknown): string[] | undefined {
if (!result || typeof result !== "object") {
return undefined;
}
const record = result as Record<string, unknown>;
const content = Array.isArray(record.content) ? record.content : null;
if (!content) {
return undefined;
}

const hasImage = content.some(
(item) =>
item && typeof item === "object" && (item as Record<string, unknown>).type === "image",
);
if (!hasImage) {
return undefined;
}

const details = record.details as Record<string, unknown> | undefined;
const path = typeof details?.path === "string" ? details.path.trim() : "";
return path ? [path] : undefined;
}

Choose a reason for hiding this comment

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

🟠 HIGH - New function extractToolResultMediaPaths lacks test coverage
Agent: testing

Category: quality

Description:
The new extractToolResultMediaPaths() function extracts image file paths from tool results. This is critical functionality for the bug fix (delivering screenshots to Telegram) but has zero test coverage. The function handles multiple edge cases that are untested.

Suggestion:
Add comprehensive unit tests covering: (1) successful extraction with valid image + details.path, (2) returns undefined when result is null/undefined, (3) returns undefined when content is not an array, (4) returns undefined when no image blocks exist, (5) returns undefined when details.path is missing/empty.

Confidence: 90%
Rule: test_coverage_new_functionality
Review ID: ab3a23fa-d862-4878-a790-f1e2a5909240
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +231 to +241
// Emit media from tool image results regardless of verbose level.
if (ctx.params.onToolResult && !isToolError) {
const mediaPaths = extractToolResultMediaPaths(result);
if (mediaPaths && mediaPaths.length > 0) {
try {
void ctx.params.onToolResult({ mediaUrls: mediaPaths });
} catch {
/* ignore */
}
}
}

Choose a reason for hiding this comment

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

🟠 HIGH - New media emission code in handleToolExecutionEnd lacks test coverage
Agent: testing

Category: quality

Description:
The new code that calls extractToolResultMediaPaths() and emits media URLs via onToolResult callback is untested. This is the core fix for the bug - emitting tool result images regardless of verbose level.

Suggestion:
Add integration tests verifying: (1) onToolResult is called with mediaUrls when tool returns image result, (2) onToolResult is NOT called when tool returns error, (3) mediaUrls array contains correct path.

Confidence: 75%
Rule: test_coverage_new_functionality
Review ID: ab3a23fa-d862-4878-a790-f1e2a5909240
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray-bot
Copy link

Review Summary

Free public review - Want AI code reviews on your PRs? Check out diffray.ai

Validated 86 issues: 25 kept, 61 filtered

Issues Found: 25

💬 See 4 individual line comment(s) for details.

📊 16 unique issue type(s) across 25 location(s)

📋 Full issue list (click to expand)

🟠 HIGH - Unhandled promise rejection in fetch operation (2 occurrences)

Agent: react

Category: bug

📍 View all locations
File Description Suggestion Confidence
extensions/matrix/src/matrix/client/config.ts:116-129 The fetch operation for Matrix login at line 116 is not wrapped in a try-catch block. Network errors... Wrap the fetch operation in a try-catch block with descriptive error handling 78%
extensions/matrix/src/matrix/client/config.ts:72 The tempClient.getUserId() call is not wrapped in a try-catch block. If the API call fails (network ... Wrap the getUserId call in a try-catch block with proper error messaging: `try { const whoami = awai... 75%

Rule: ts_handle_async_operations_with_proper_erro


🟠 HIGH - Array access without bounds check on mediaList[0]

Agent: bugs

Category: bug

File: extensions/feishu/src/bot.ts:474-476

Description: The code accesses mediaList[0] without checking if the array is non-empty. If buildFeishuMediaPayload is called with an empty array, first will be undefined.

Suggestion: Check array length before access: const first = mediaList.length > 0 ? mediaList[0] : undefined;

Confidence: 72%

Rule: bug_array_bounds


🟠 HIGH - Large function with multiple responsibilities (425 lines) (3 occurrences)

Agent: architecture

Category: quality

📍 View all locations
File Description Suggestion Confidence
extensions/feishu/src/bot.ts:522-947 The handleFeishuMessage function is 425+ lines with multiple distinct responsibilities: message dedu... Break into focused functions: validateAndDeduplicateMessage(), resolveMessageContext(), enforceAcces... 88%
extensions/googlechat/src/monitor.ts:417-765 The processMessageWithPipeline function is 348 lines handling bot detection, media download, policy ... Extract separate functions for each concern: validateMessageSource(), enforceAccessControl(), resolv... 85%
extensions/matrix/src/matrix/monitor/index.ts:31-339 The monitorMatrixProvider function is 308 lines handling config resolution, allowlist normalization,... Extract: resolveAndNormalizeConfig() for the allowlist/room resolution (lines 124-220), initializeMa... 82%

Rule: arch_extract_complex_logic


🟠 HIGH - Multiple JSON.parse operations on untrusted data

Agent: bugs

Category: bug

File: extensions/bluebubbles/src/monitor.ts:549-555

Description: The code performs nested JSON.parse operations on webhook data. While inside a try-catch, the error handling may not differentiate between parse errors at different stages.

Suggestion: Use individual try-catch blocks for each JSON.parse to provide more specific error messages for debugging.

Confidence: 68%

Rule: bug_missing_try_catch


🟠 HIGH - New function extractToolResultMediaPaths lacks test coverage (2 occurrences)

Agent: testing

Category: quality

📍 View all locations
File Description Suggestion Confidence
src/agents/pi-embedded-subscribe.tools.ts:121-142 The new extractToolResultMediaPaths() function extracts image file paths from tool results. This is ... Add comprehensive unit tests covering: (1) successful extraction with valid image + details.path, (2... 90%
src/agents/pi-embedded-subscribe.handlers.tools.ts:231-241 The new code that calls extractToolResultMediaPaths() and emits media URLs via onToolResult callback... Add integration tests verifying: (1) onToolResult is called with mediaUrls when tool returns image r... 75%

Rule: test_coverage_new_functionality


🟠 HIGH - Unsafe any type in API response

Agent: typescript

Category: bug

File: extensions/feishu/src/bot.ts:126

Description: Using 'any' type for Feishu API response bypasses all type checking. While optional chaining is used, a proper interface would provide better maintainability.

Suggestion: Define a proper interface for the expected response structure: interface FeishuUserResponse { data?: { user?: { name?: string; display_name?: string; nickname?: string; en_name?: string } } }

Confidence: 65%

Rule: ts_prefer_unknown_over_any


🟡 MEDIUM - Multiple fallback behaviors with unclear contract

Agent: architecture

Category: quality

File: extensions/feishu/src/media.ts:49-94

Description: The downloadImageFeishu function silently tries 7+ different response formats without documenting which is expected or why fallbacks exist. This makes debugging difficult.

Suggestion: Document the expected SDK response format explicitly. Add logging when fallbacks are used so the unexpected format is visible.

Confidence: 75%

Rule: arch_unclear_api_contract


🟡 MEDIUM - Missing URL validation before external fetch (2 occurrences)

Agent: security

Category: security

📍 View all locations
File Description Suggestion Confidence
extensions/feishu/src/docx.ts:170-175 The downloadImage function fetches from user-controlled URLs without validation. This could enable S... Validate URLs before fetching: 1) Use URL constructor to parse, 2) Check protocol is http/https only... 85%
extensions/feishu/src/media.ts:496-503 The sendMediaFeishu function fetches from remote URLs without validation, similar to the docx.ts iss... Validate URLs before fetching, checking protocol and blocklisting private IP ranges and cloud metada... 80%

Rule: security_missing_input_validation


🟡 MEDIUM - Nullish coalescing preserves invalid 0 value for textChunkLimit

Agent: bugs

Category: bug

File: extensions/googlechat/src/monitor.ts:871

Description: The code uses account.config.textChunkLimit ?? 4000 which will preserve an explicitly configured value of 0. Since 0 is not a valid chunk limit (cannot chunk text into 0-byte segments), this could cause runtime errors or infinite loops in the chunking logic.

Suggestion: Use a guard to ensure the value is positive: Math.max(1, account.config.textChunkLimit ?? 4000) or add validation: (account.config.textChunkLimit && account.config.textChunkLimit > 0) ? account.config.textChunkLimit : 4000

Confidence: 70%

Rule: js_nullish_coalescing_numeric


🟡 MEDIUM - Swallowed error in file cleanup

Agent: react

Category: bug

File: extensions/feishu/src/media.ts:73-74

Description: The fs.promises.unlink() cleanup operation has an empty catch block that silently swallows errors. While cleanup errors may be less critical, they should at least be logged for debugging purposes.

Suggestion: Log the cleanup error instead of silently swallowing it: .catch((err) => console.debug('cleanup failed:', err))

Confidence: 65%

Rule: ts_re_throw_or_return_errors_to_propagate_f


🟡 MEDIUM - Floating promise in webhook mode lacks void operator

Agent: nodejs

Category: bug

File: extensions/feishu/src/monitor.ts:62-68

Description: In webhook mode (fireAndForget=true), handleFeishuMessage() is called and the returned promise is handled with .catch() but not awaited. While intentional for webhook timeout, using void operator would clarify intent.

Suggestion: Mark the intentional fire-and-forget with void operator for clarity: void promise.catch((err) => { ... });

Confidence: 60%

Rule: node_floating_promise


🟡 MEDIUM - Silent error handling without logging (4 occurrences)

Agent: react

Category: quality

📍 View all locations
File Description Suggestion Confidence
extensions/feishu/src/bot.ts:211-213 The catch block in parseMessageContent() silently returns a fallback value without logging the parsi... Add error logging before returning the fallback:
} catch (err) {
  log?.(`feishu: fail... | 75% |
| `extensions/feishu/src/bot.ts:264-266` | The catch block in parseMediaKeys() silently returns an empty object without logging the JSON parsin... | Add error logging with context:
```typescript
} catch (err) {
  log?.(`feishu: failed to parse media... | 75% |
| `extensions/feishu/src/bot.ts:308-310` | The catch block in parsePostContent() silently returns a Chinese fallback message without logging th... | Add error logging:
```typescript
} catch (err) {
  log?.(`feishu: failed to parse post content: ${St... | 75% |
| `extensions/googlechat/src/monitor.ts:80-82` | The catch block in resolveWebhookPath() silently returns null when URL parsing fails. This makes it ... | Add error logging:
```typescript
} catch (err) {
  runtime.log?.(`googlechat: failed to parse webhoo... | 70% |

</details>

**Rule:** `ts_log_errors_instead_of_failing_silently`

---

#### 🟡 MEDIUM - Raw API error message exposed to user (2 occurrences)

**Agent:** [security](https://diffray.ai/agents/security)

**Category:** security

<details>
<summary>📍 View all locations</summary>

| File | Description | Suggestion | Confidence |
|------|-------------|------------|------------|
| `extensions/feishu/src/docx.ts:86-88` | The error thrown exposes the raw error message from the Feishu API (res.msg) directly. This could le... | Wrap with generic message and log original: throw new Error('Failed to convert document content'); /... | 70% |
| `extensions/matrix/src/matrix/client/config.ts:127-130` | The Matrix login error exposes raw error response text from the homeserver directly. This could leak... | Map server error messages to generic user-friendly messages. Log original errorText for debugging. | 70% |

</details>

**Rule:** `sec_internal_details_in_user_errors`

---

#### 🟡 MEDIUM - Catch block without error context in logging

**Agent:** refactoring

**Category:** quality

**File:** `extensions/bluebubbles/src/monitor.ts:1115-1126`

**Description:** The catch block in resolveBlueBubblesAckReaction() logs a message but doesn't include the actual error details. This makes it difficult to debug why reaction normalization is failing.

**Suggestion:** Include the error in the log message: `ack reaction skipped (unsupported for BlueBubbles): ${raw}, error: ${String(err)}`

**Confidence:** 70%

**Rule:** `ts_include_error_context_in_structured_logs`

---

#### 🟡 MEDIUM - Non-null assertion without validation

**Agent:** typescript

**Category:** quality

**File:** `extensions/bluebubbles/src/monitor.ts:49-50`

**Description:** Using non-null assertion (!) on Map.keys().next().value after size check. While logically safe due to size >= DEDUP_MAX_SIZE check on line 48, the assertion could mask issues if map is mutated concurrently.

**Suggestion:** Add explicit check: const first = processedMessageIds.keys().next().value; if (first) { processedMessageIds.delete(first); }

**Confidence:** 65%

**Rule:** `ts_non_null_assertion`

---

#### 🔵 LOW - Duplicated response format handling logic

**Agent:** [quality](https://diffray.ai/agents/quality)

**Category:** quality

**File:** `extensions/feishu/src/media.ts:132-177`

**Description:** The downloadMessageResourceFeishu function duplicates the exact same response format handling logic as downloadImageFeishu. This is a clear DRY violation that increases maintenance burden.

**Suggestion:** Extract the response format handling into a shared helper function: `async function extractBufferFromFeishuResponse(response: any): Promise<Buffer>` and call it from both functions.

**Confidence:** 90%

**Rule:** `quality_early_return_instead_nested`

---

</details>

<details>
<summary>ℹ️ 21 issue(s) outside PR diff (click to expand)</summary>

> These issues were found in lines not modified in this PR.


#### 🟠 HIGH - Unhandled promise rejection in fetch operation (2 occurrences)

**Agent:** react

**Category:** bug

<details>
<summary>📍 View all locations</summary>

| File | Description | Suggestion | Confidence |
|------|-------------|------------|------------|
| `extensions/matrix/src/matrix/client/config.ts:116-129` | The fetch operation for Matrix login at line 116 is not wrapped in a try-catch block. Network errors... | Wrap the fetch operation in a try-catch block with descriptive error handling | 78% |
| `extensions/matrix/src/matrix/client/config.ts:72` | The tempClient.getUserId() call is not wrapped in a try-catch block. If the API call fails (network ... | Wrap the getUserId call in a try-catch block with proper error messaging: `try { const whoami = awai... | 75% |

</details>

**Rule:** `ts_handle_async_operations_with_proper_erro`

---

#### 🟠 HIGH - Array access without bounds check on mediaList[0]

**Agent:** [bugs](https://diffray.ai/agents/bugs)

**Category:** bug

**File:** `extensions/feishu/src/bot.ts:474-476`

**Description:** The code accesses `mediaList[0]` without checking if the array is non-empty. If `buildFeishuMediaPayload` is called with an empty array, first will be undefined.

**Suggestion:** Check array length before access: `const first = mediaList.length > 0 ? mediaList[0] : undefined;`

**Confidence:** 72%

**Rule:** `bug_array_bounds`

---

#### 🟠 HIGH - Large function with multiple responsibilities (425 lines) (3 occurrences)

**Agent:** [architecture](https://diffray.ai/agents/architecture)

**Category:** quality

<details>
<summary>📍 View all locations</summary>

| File | Description | Suggestion | Confidence |
|------|-------------|------------|------------|
| `extensions/feishu/src/bot.ts:522-947` | The handleFeishuMessage function is 425+ lines with multiple distinct responsibilities: message dedu... | Break into focused functions: validateAndDeduplicateMessage(), resolveMessageContext(), enforceAcces... | 88% |
| `extensions/googlechat/src/monitor.ts:417-765` | The processMessageWithPipeline function is 348 lines handling bot detection, media download, policy ... | Extract separate functions for each concern: validateMessageSource(), enforceAccessControl(), resolv... | 85% |
| `extensions/matrix/src/matrix/monitor/index.ts:31-339` | The monitorMatrixProvider function is 308 lines handling config resolution, allowlist normalization,... | Extract: resolveAndNormalizeConfig() for the allowlist/room resolution (lines 124-220), initializeMa... | 82% |

</details>

**Rule:** `arch_extract_complex_logic`

---

#### 🟠 HIGH - Multiple JSON.parse operations on untrusted data

**Agent:** [bugs](https://diffray.ai/agents/bugs)

**Category:** bug

**File:** `extensions/bluebubbles/src/monitor.ts:549-555`

**Description:** The code performs nested JSON.parse operations on webhook data. While inside a try-catch, the error handling may not differentiate between parse errors at different stages.

**Suggestion:** Use individual try-catch blocks for each JSON.parse to provide more specific error messages for debugging.

**Confidence:** 68%

**Rule:** `bug_missing_try_catch`

---

#### 🟠 HIGH - Unsafe any type in API response

**Agent:** typescript

**Category:** bug

**File:** `extensions/feishu/src/bot.ts:126`

**Description:** Using 'any' type for Feishu API response bypasses all type checking. While optional chaining is used, a proper interface would provide better maintainability.

**Suggestion:** Define a proper interface for the expected response structure: interface FeishuUserResponse { data?: { user?: { name?: string; display_name?: string; nickname?: string; en_name?: string } } }

**Confidence:** 65%

**Rule:** `ts_prefer_unknown_over_any`

---

#### 🟡 MEDIUM - Multiple fallback behaviors with unclear contract

**Agent:** [architecture](https://diffray.ai/agents/architecture)

**Category:** quality

**File:** `extensions/feishu/src/media.ts:49-94`

**Description:** The downloadImageFeishu function silently tries 7+ different response formats without documenting which is expected or why fallbacks exist. This makes debugging difficult.

**Suggestion:** Document the expected SDK response format explicitly. Add logging when fallbacks are used so the unexpected format is visible.

**Confidence:** 75%

**Rule:** `arch_unclear_api_contract`

---

#### 🟡 MEDIUM - Missing URL validation before external fetch (2 occurrences)

**Agent:** [security](https://diffray.ai/agents/security)

**Category:** security

<details>
<summary>📍 View all locations</summary>

| File | Description | Suggestion | Confidence |
|------|-------------|------------|------------|
| `extensions/feishu/src/docx.ts:170-175` | The downloadImage function fetches from user-controlled URLs without validation. This could enable S... | Validate URLs before fetching: 1) Use URL constructor to parse, 2) Check protocol is http/https only... | 85% |
| `extensions/feishu/src/media.ts:496-503` | The sendMediaFeishu function fetches from remote URLs without validation, similar to the docx.ts iss... | Validate URLs before fetching, checking protocol and blocklisting private IP ranges and cloud metada... | 80% |

</details>

**Rule:** `security_missing_input_validation`

---

#### 🟡 MEDIUM - Nullish coalescing preserves invalid 0 value for textChunkLimit

**Agent:** [bugs](https://diffray.ai/agents/bugs)

**Category:** bug

**File:** `extensions/googlechat/src/monitor.ts:871`

**Description:** The code uses `account.config.textChunkLimit ?? 4000` which will preserve an explicitly configured value of 0. Since 0 is not a valid chunk limit (cannot chunk text into 0-byte segments), this could cause runtime errors or infinite loops in the chunking logic.

**Suggestion:** Use a guard to ensure the value is positive: `Math.max(1, account.config.textChunkLimit ?? 4000)` or add validation: `(account.config.textChunkLimit && account.config.textChunkLimit > 0) ? account.config.textChunkLimit : 4000`

**Confidence:** 70%

**Rule:** `js_nullish_coalescing_numeric`

---

#### 🟡 MEDIUM - Swallowed error in file cleanup

**Agent:** react

**Category:** bug

**File:** `extensions/feishu/src/media.ts:73-74`

**Description:** The fs.promises.unlink() cleanup operation has an empty catch block that silently swallows errors. While cleanup errors may be less critical, they should at least be logged for debugging purposes.

**Suggestion:** Log the cleanup error instead of silently swallowing it: `.catch((err) => console.debug('cleanup failed:', err))`

**Confidence:** 65%

**Rule:** `ts_re_throw_or_return_errors_to_propagate_f`

---

#### 🟡 MEDIUM - Floating promise in webhook mode lacks void operator

**Agent:** nodejs

**Category:** bug

**File:** `extensions/feishu/src/monitor.ts:62-68`

**Description:** In webhook mode (fireAndForget=true), handleFeishuMessage() is called and the returned promise is handled with .catch() but not awaited. While intentional for webhook timeout, using void operator would clarify intent.

**Suggestion:** Mark the intentional fire-and-forget with void operator for clarity: `void promise.catch((err) => { ... });`

**Confidence:** 60%

**Rule:** `node_floating_promise`

---

#### 🟡 MEDIUM - Silent error handling without logging (3 occurrences)

**Agent:** react

**Category:** quality

<details>
<summary>📍 View all locations</summary>

| File | Description | Suggestion | Confidence |
|------|-------------|------------|------------|
| `extensions/feishu/src/bot.ts:211-213` | The catch block in parseMessageContent() silently returns a fallback value without logging the parsi... | Add error logging before returning the fallback:
```typescript
} catch (err) {
  log?.(`feishu: fail... | 75% |
| `extensions/feishu/src/bot.ts:264-266` | The catch block in parseMediaKeys() silently returns an empty object without logging the JSON parsin... | Add error logging with context:
```typescript
} catch (err) {
  log?.(`feishu: failed to parse media... | 75% |
| `extensions/feishu/src/bot.ts:308-310` | The catch block in parsePostContent() silently returns a Chinese fallback message without logging th... | Add error logging:
```typescript
} catch (err) {
  log?.(`feishu: failed to parse post content: ${St... | 75% |

</details>

**Rule:** `ts_log_errors_instead_of_failing_silently`

---

#### 🟡 MEDIUM - Raw API error message exposed to user (2 occurrences)

**Agent:** [security](https://diffray.ai/agents/security)

**Category:** security

<details>
<summary>📍 View all locations</summary>

| File | Description | Suggestion | Confidence |
|------|-------------|------------|------------|
| `extensions/feishu/src/docx.ts:86-88` | The error thrown exposes the raw error message from the Feishu API (res.msg) directly. This could le... | Wrap with generic message and log original: throw new Error('Failed to convert document content'); /... | 70% |
| `extensions/matrix/src/matrix/client/config.ts:127-130` | The Matrix login error exposes raw error response text from the homeserver directly. This could leak... | Map server error messages to generic user-friendly messages. Log original errorText for debugging. | 70% |

</details>

**Rule:** `sec_internal_details_in_user_errors`

---

#### 🟡 MEDIUM - Catch block without error context in logging

**Agent:** refactoring

**Category:** quality

**File:** `extensions/bluebubbles/src/monitor.ts:1115-1126`

**Description:** The catch block in resolveBlueBubblesAckReaction() logs a message but doesn't include the actual error details. This makes it difficult to debug why reaction normalization is failing.

**Suggestion:** Include the error in the log message: `ack reaction skipped (unsupported for BlueBubbles): ${raw}, error: ${String(err)}`

**Confidence:** 70%

**Rule:** `ts_include_error_context_in_structured_logs`

---

#### 🟡 MEDIUM - Non-null assertion without validation

**Agent:** typescript

**Category:** quality

**File:** `extensions/bluebubbles/src/monitor.ts:49-50`

**Description:** Using non-null assertion (!) on Map.keys().next().value after size check. While logically safe due to size >= DEDUP_MAX_SIZE check on line 48, the assertion could mask issues if map is mutated concurrently.

**Suggestion:** Add explicit check: const first = processedMessageIds.keys().next().value; if (first) { processedMessageIds.delete(first); }

**Confidence:** 65%

**Rule:** `ts_non_null_assertion`

---

</details>

<details>
<summary>🔇 2 low-severity issue(s) not posted (min: medium)</summary>

> Issues below `medium` severity are saved but not posted as comments.
> View all issues in the full review details.

</details>

🔗 [View full review details](https://app.diffray.ai/reviews/ab3a23fa-d862-4878-a790-f1e2a5909240)

---
<sub>Review ID: `ab3a23fa-d862-4878-a790-f1e2a5909240`</sub>
<sub>Rate it 👍 or 👎 to improve future reviews | Powered by <a href="https://diffray.ai?utm_source=github-private-note">diffray</a></sub>

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +121 to +142
export function extractToolResultMediaPaths(result: unknown): string[] | undefined {
if (!result || typeof result !== "object") {
return undefined;
}
const record = result as Record<string, unknown>;
const content = Array.isArray(record.content) ? record.content : null;
if (!content) {
return undefined;
}

const hasImage = content.some(
(item) =>
item && typeof item === "object" && (item as Record<string, unknown>).type === "image",
);
if (!hasImage) {
return undefined;
}

const details = record.details as Record<string, unknown> | undefined;
const path = typeof details?.path === "string" ? details.path.trim() : "";
return path ? [path] : undefined;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

function only extracts details.path once, but multiple images could exist in the content array

If a tool result contains multiple image content blocks, only the first details.path is returned. Consider iterating through all image content blocks or clarifying that only single-image results are supported.

Currently:

const hasImage = content.some(...)  // checks if ANY image exists
return path ? [path] : undefined;   // but only returns ONE path
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-subscribe.tools.ts
Line: 121:142

Comment:
function only extracts `details.path` once, but multiple images could exist in the `content` array

If a tool result contains multiple image content blocks, only the first `details.path` is returned. Consider iterating through all image content blocks or clarifying that only single-image results are supported.

Currently:
```typescript
const hasImage = content.some(...)  // checks if ANY image exists
return path ? [path] : undefined;   // but only returns ONE path
```

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

Comment on lines +121 to +142
export function extractToolResultMediaPaths(result: unknown): string[] | undefined {
if (!result || typeof result !== "object") {
return undefined;
}
const record = result as Record<string, unknown>;
const content = Array.isArray(record.content) ? record.content : null;
if (!content) {
return undefined;
}

const hasImage = content.some(
(item) =>
item && typeof item === "object" && (item as Record<string, unknown>).type === "image",
);
if (!hasImage) {
return undefined;
}

const details = record.details as Record<string, unknown> | undefined;
const path = typeof details?.path === "string" ? details.path.trim() : "";
return path ? [path] : undefined;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

add unit tests for extractToolResultMediaPaths to cover edge cases (multiple images, missing path, invalid inputs)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

Comment:
add unit tests for `extractToolResultMediaPaths` to cover edge cases (multiple images, missing path, invalid inputs)

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

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

@christianklotz
Copy link
Contributor

Closing because superseded by #16679 which is based on this PR. @strelov1 Thank you!

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: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Agent read tool returns image but not sent to Telegram channel

3 participants

Comments