Skip to content

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

Description

@c0ffeedev

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

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions