Skip to content

[Bug]: Malformed tool result content block crashes context truncation in a session-permanent loop #34979

@coffeexcoin

Description

@coffeexcoin

Bug type

Crash (process/app exits or hangs)

Summary

A plugin tool handler that returns undefined/void produces a malformed content block {type: "text"} (no text property), which crashes the context truncation pipeline on every subsequent message and locks the session into an unrecoverable crash loop.

Steps to reproduce

  1. Install or enable a plugin whose tool handler returns undefined for at least one action (e.g. sentinel plugin's remove action has no return statement).
  2. Invoke that tool action in an agent session — for example, call sentinel_control with action: "remove".
  3. The tool result is persisted to the session JSONL as content: [{"type": "text"}] (missing text key).
  4. Send any follow-up message to the session.

Expected behavior

The session continues normally. If a tool handler returns undefined, the framework should either default the content to an empty string or reject the malformed block before persistence.

Actual behavior

Every subsequent message triggers:

TypeError: Cannot read properties of undefined (reading 'length')
    at estimateMessageChars (reply-DhtejUNZ.js:76200:76)

The crash repeats on every incoming message because the malformed content block is already persisted in the session JSONL. The agent becomes completely unresponsive. In the observed incident, this lasted ~17 minutes until the session was manually reset.

Full crash path: enforceToolResultContextBudgetInPlacetruncateToolResultToCharsestimateMessageCharsisTextBlock returns true for {type: "text"}block.text.length throws on undefined.

OpenClaw version

2026.3.2

Operating system

macOS

Install method

npm global

Logs, screenshots, and evidence

Session transcript timeline showing the corrupted tool result:

| Time (UTC) | Action | Result |
|---|---|---|
| 19:10:49 | list | Valid (`[]`) |
| 19:10:55 | add | Valid error (wrong strategy) |
| 19:11:18 | add | Valid error (host not allowed) |
| 19:11:42 | add | Valid error (host not allowed) |
| 19:12:52 | add | Success — created `timeapi-test-2361` |
| 19:13:06 | status | Success |
| 19:13:09 | get | Success |
| 19:13:12 | list | Success |
| 19:13:17 | remove | **Corrupted**`{"type":"text"}` with no `text` |

The corrupted tool result message as persisted in the session JSONL:


{
  "role": "toolResult",
  "toolCallId": "call_iuCGHR5hjNegUaPHMjNIgeRL|fc_0f8c6651728332d20169a8844d498481929d39e79247a406d5",
  "toolName": "sentinel_control",
  "content": [{ "type": "text" }],
  "isError": false
}


Every other `sentinel_control` action (`list`, `add`, `status`, `get`) returned well-formed `{"type": "text", "text": "..."}` blocks. Only `remove` produced the malformed response.

Impact and severity

  • Affected users/systems/channels: Any session using a plugin whose tool handler returns void/undefined for any action. The sentinel plugin's remove action is a known trigger, but any plugin with a missing return statement can cause this.
  • Severity: Blocks workflow. The session becomes permanently unresponsive until manually reset. No data loss, but all in-flight conversation context is effectively lost.
  • Frequency: Edge case per plugin action, but deterministic once triggered — every session that hits the codepath will crash, and every subsequent message in that session will crash again.
  • Consequence: Complete session lockout. In the observed incident, the agent was unresponsive for ~17 minutes. Users have no self-service recovery path other than resetting the session.

Additional information

There are two independent bugs:

  1. isTextBlock type guard (src/agents/pi-embedded-runner/tool-result-char-estimator.ts): Only checks block.type === "text" without verifying block.text is a string. This lets malformed blocks pass the guard, then block.text.length throws.

  2. No content block validation at persistence (src/agents/session-tool-result-guard.ts): Malformed content blocks are persisted to session JSONL without sanitization. Once persisted, the crash triggers on every session load — there is no recovery without manual session reset or JSONL editing.

The plugin under development also needed a fix to properly return a string, but the core framework should be resilient to any plugin returning undefined.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:crashProcess/app exits unexpectedly or hangsstaleMarked as stale due to inactivity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions