Skip to content

fix: guard against malformed text blocks in context truncation#39331

Closed
alvinttang wants to merge 2 commits intoopenclaw:mainfrom
alvinttang:fix/34979-malformed-text-block-crash
Closed

fix: guard against malformed text blocks in context truncation#39331
alvinttang wants to merge 2 commits intoopenclaw:mainfrom
alvinttang:fix/34979-malformed-text-block-crash

Conversation

@alvinttang
Copy link
Copy Markdown
Contributor

Summary

  • Fix session-permanent crash loop caused by malformed tool result content blocks ({type: "text"} missing text property)
  • When a plugin tool handler returns undefined, the framework persists {type: "text"} without a text property to the session JSONL
  • isTextBlock() type guard only checked type === "text" without verifying typeof text === "string", causing block.text.length to throw TypeError on every subsequent message
  • Also added the same defensive check in the context pruning estimator

Fixes #34979

Test plan

  • Verify sessions with malformed {type: "text"} content blocks no longer crash
  • Verify normal text blocks still estimate correctly
  • Verify context truncation still works for valid tool results

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 8, 2026

Greptile Summary

This PR attempts to fix a session-crashing TypeError caused by malformed {type: "text"} content blocks (missing the text property). The fix to estimateTextAndImageChars in pruner.ts and isTextBlock in tool-result-char-estimator.ts is correct, but the PR leaves a critical vulnerability: collectTextSegments in pruner.ts (line 19) lacks the equivalent defensive check. When this function encounters a malformed block, it pushes undefined into the parts array, which then flows to estimateJoinedTextLength, takeHeadFromJoinedText, and takeTailFromJoinedText — all of which will crash with TypeError when attempting to access .length on undefined. This means sessions with malformed blocks will still crash during the soft-trim phase of context pruning, even after this PR is merged.

Confidence Score: 1/5

  • This PR incompletely fixes the crash—the core issue persists in collectTextSegments, which will still crash when soft-trimming processes malformed tool result blocks.
  • The PR adds defensive guards in two places (estimateTextAndImageChars and isTextBlock) but critically misses the same guard in collectTextSegments. This function pushes undefined into an array when encountering a malformed block, causing guaranteed crashes downstream in estimateJoinedTextLength, takeHeadFromJoinedText, and takeTailFromJoinedText. The PR claims to fix a crash loop but leaves an active vector for the same crash.
  • src/agents/pi-extensions/context-pruning/pruner.ts — collectTextSegments at line 19 requires the same defensive guard as estimateTextAndImageChars

Comments Outside Diff (1)

  1. src/agents/pi-extensions/context-pruning/pruner.ts, line 19-21 (link)

    The PR adds a typeof block.text === "string" guard to estimateTextAndImageChars (line 102), but the same malformed block vulnerability exists here in collectTextSegments.

    When a malformed block {type: "text"} with no text property is encountered, block.text is undefined and gets pushed into the parts array. This undefined then crashes downstream functions that access .length:

    • Line 32 in estimateJoinedTextLength: len += p.lengthTypeError
    • Line 54 in takeHeadFromJoinedText: if (p.length <= remaining)TypeError
    • Line 73 in takeTailFromJoinedText: if (p.length <= remaining)TypeError

    The call chain is: softTrimToolResultMessage (line 198) → collectTextSegmentsestimateJoinedTextLength (line 199), so any session with soft-trimming enabled will crash when processing a malformed tool result block.

Last reviewed commit: 224eabe

@alvinttang
Copy link
Copy Markdown
Contributor Author

Fixed — added typeof block.text === "string" guard to collectTextSegments to prevent undefined from being pushed into the parts array.

gambletan and others added 2 commits March 8, 2026 17:04
When a plugin tool handler returns undefined, the framework persists a
malformed content block {type: "text"} (missing text property) to the
session JSONL. The isTextBlock type guard only checked type === "text"
without verifying typeof text === "string", causing block.text.length
to throw TypeError on every subsequent message, locking the session
into a permanent crash loop.

Add string type check for the text property in isTextBlock and the
context pruning estimator to gracefully handle malformed blocks.

Fixes openclaw#34979

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 23, 2026
BunsDev pushed a commit to cgdusek/openclaw that referenced this pull request Apr 25, 2026
…ar estimation

A plugin tool handler returning undefined produces {type: "text"} (no text
property) in the session JSONL. This crashes the context truncation pipeline
with TypeError on every subsequent message, locking the session into an
unrecoverable crash loop.

Guard all four crash sites:
- isTextBlock type guard in tool-result-char-estimator.ts
- collectTextSegments in pruner.ts
- collectPrunableToolResultSegments in pruner.ts
- estimateTextAndImageChars in pruner.ts

Add regression tests for both the char estimator and the context pruner
covering malformed toolResult content blocks.

Closes openclaw#34979

Based on prior work by @alvinttang (openclaw#39331) and @coffeexcoin (openclaw#34980).

Co-Authored-By: alvinttang <alvinttang@users.noreply.github.com>
Co-Authored-By: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BunsDev pushed a commit that referenced this pull request Apr 25, 2026
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes #34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by #39331 and #34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@BunsDev
Copy link
Copy Markdown
Member

BunsDev commented Apr 25, 2026

Closing this as superseded by #51267, which has now landed on main as 6b38714.

#51267 includes this fix, the missing pruner paths, null-entry guards, regression tests, and the maintainer follow-up for malformed non-string text blocks so pruning does not undercount them as zero-sized. The squash body credits this PR as prior work.

@BunsDev BunsDev added the close:superseded PR close reason label Apr 25, 2026
@BunsDev BunsDev closed this Apr 25, 2026
vincentkoc added a commit that referenced this pull request Apr 25, 2026
… tool-result pruning entries

Three entries were missing co-credits I should have preserved:

- Diagnostics/OTEL exec-process spans (#71451): @vincentkoc implemented,
  but @jlapenna's #70424 proposed the broader tracing work this entry
  builds on. Now credits both.
- Diagnostics/OTEL preloaded SDK (#71450): same pattern — credits
  @vincentkoc and @jlapenna.
- Agents/tool-result pruning (#51267): @cgdusek's PR explicitly built
  on prior work in #39331 by @alvinttang and #34980 by @coffeexcoin.
  Now credits all three.
steipete pushed a commit to MonkeyLeeT/openclaw that referenced this pull request Apr 25, 2026
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes openclaw#34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by openclaw#39331 and openclaw#34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
steipete pushed a commit to MonkeyLeeT/openclaw that referenced this pull request Apr 25, 2026
… tool-result pruning entries

Three entries were missing co-credits I should have preserved:

- Diagnostics/OTEL exec-process spans (openclaw#71451): @vincentkoc implemented,
  but @jlapenna's openclaw#70424 proposed the broader tracing work this entry
  builds on. Now credits both.
- Diagnostics/OTEL preloaded SDK (openclaw#71450): same pattern — credits
  @vincentkoc and @jlapenna.
- Agents/tool-result pruning (openclaw#51267): @cgdusek's PR explicitly built
  on prior work in openclaw#39331 by @alvinttang and openclaw#34980 by @coffeexcoin.
  Now credits all three.
Angfr95 pushed a commit to Angfr95/openclaw that referenced this pull request Apr 25, 2026
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes openclaw#34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by openclaw#39331 and openclaw#34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Angfr95 pushed a commit to Angfr95/openclaw that referenced this pull request Apr 25, 2026
… tool-result pruning entries

Three entries were missing co-credits I should have preserved:

- Diagnostics/OTEL exec-process spans (openclaw#71451): @vincentkoc implemented,
  but @jlapenna's openclaw#70424 proposed the broader tracing work this entry
  builds on. Now credits both.
- Diagnostics/OTEL preloaded SDK (openclaw#71450): same pattern — credits
  @vincentkoc and @jlapenna.
- Agents/tool-result pruning (openclaw#51267): @cgdusek's PR explicitly built
  on prior work in openclaw#39331 by @alvinttang and openclaw#34980 by @coffeexcoin.
  Now credits all three.
ayesha-aziz123 pushed a commit to ayesha-aziz123/openclaw that referenced this pull request Apr 26, 2026
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes openclaw#34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by openclaw#39331 and openclaw#34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ayesha-aziz123 pushed a commit to ayesha-aziz123/openclaw that referenced this pull request Apr 26, 2026
… tool-result pruning entries

Three entries were missing co-credits I should have preserved:

- Diagnostics/OTEL exec-process spans (openclaw#71451): @vincentkoc implemented,
  but @jlapenna's openclaw#70424 proposed the broader tracing work this entry
  builds on. Now credits both.
- Diagnostics/OTEL preloaded SDK (openclaw#71450): same pattern — credits
  @vincentkoc and @jlapenna.
- Agents/tool-result pruning (openclaw#51267): @cgdusek's PR explicitly built
  on prior work in openclaw#39331 by @alvinttang and openclaw#34980 by @coffeexcoin.
  Now credits all three.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes openclaw#34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by openclaw#39331 and openclaw#34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
… tool-result pruning entries

Three entries were missing co-credits I should have preserved:

- Diagnostics/OTEL exec-process spans (openclaw#71451): @vincentkoc implemented,
  but @jlapenna's openclaw#70424 proposed the broader tracing work this entry
  builds on. Now credits both.
- Diagnostics/OTEL preloaded SDK (openclaw#71450): same pattern — credits
  @vincentkoc and @jlapenna.
- Agents/tool-result pruning (openclaw#51267): @cgdusek's PR explicitly built
  on prior work in openclaw#39331 by @alvinttang and openclaw#34980 by @coffeexcoin.
  Now credits all three.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Harden context pruning and tool-result character estimation against malformed `{ type: "text" }` blocks created by void/undefined tool handler results.

- Require text blocks to carry a string before using `.length` in the tool-result estimator.
- Guard context-pruning text/image loops against malformed and null content entries.
- Serialize malformed non-string text blocks for pruning size accounting so they cannot bypass trimming as zero-sized.
- Add regression coverage for malformed text blocks, null entries, and non-string text payloads.

Closes openclaw#34979.

Maintainer verification:
- `pnpm test src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts src/agents/pi-hooks/context-pruning/pruner.test.ts`
- `pnpm check:changed`
- GitHub checks passed, including the OpenAI / Opus 4.6 parity gate.

Based on prior work by openclaw#39331 and openclaw#34980.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
Co-authored-by: alvinttang <alvinttang@users.noreply.github.com>
Co-authored-by: coffeexcoin <coffeexcoin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
… tool-result pruning entries

Three entries were missing co-credits I should have preserved:

- Diagnostics/OTEL exec-process spans (openclaw#71451): @vincentkoc implemented,
  but @jlapenna's openclaw#70424 proposed the broader tracing work this entry
  builds on. Now credits both.
- Diagnostics/OTEL preloaded SDK (openclaw#71450): same pattern — credits
  @vincentkoc and @jlapenna.
- Agents/tool-result pruning (openclaw#51267): @cgdusek's PR explicitly built
  on prior work in openclaw#39331 by @alvinttang and openclaw#34980 by @coffeexcoin.
  Now credits all three.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling close:superseded PR close reason size: XS stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants