fix: guard against malformed text blocks in context truncation#51267
fix: guard against malformed text blocks in context truncation#51267BunsDev merged 4 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR hardens the context-truncation pipeline against a class of malformed content blocks produced when a plugin tool handler returns Key changes:
The logic changes are minimal, consistent, and defensive without altering any behavior for well-formed blocks. One minor test assertion in Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/pi-embedded-runner/tool-result-char-estimator.test.ts
Line: 30
Comment:
**Test assertion doesn't match comment intent**
The comment says the malformed block should be estimated "not zero" (i.e. the `estimateUnknownChars` fallback should fire), but `toBeGreaterThanOrEqual(0)` allows a result of `0` — which would silently pass even if the fallback were broken and returned nothing. The assertion should be `toBeGreaterThan(0)` to actually enforce the stated invariant.
For reference, `estimateUnknownChars({type:"text"})` will `JSON.stringify` the object to `'{"type":"text"}'` (15 chars), so the expected result is already well above zero.
```suggestion
expect(estimateMessageCharsCached(malformed, cache)).toBeGreaterThan(0);
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fix: guard against m..." |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0a2700b2e
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed both review comments in b5cd499: Greptile (P2) — test assertion: Tightened from Codex (P1) — null content entries: Added |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: adca2808f3
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Re: Codex P1 — Preserve size accounting for malformed text blocks Valid defense-in-depth observation, but out of scope for this crash fix: Before this PR: After this PR: malformed block is silently skipped in the pruner's size estimate → minor undercount, but the session is functional and recoverable. The char estimator ( |
CI status — remaining failures are pre-existing / unrelatedAll PR-scoped checks pass (lint, build, unit tests). The 3 remaining red jobs are unrelated to this change:
This PR only modifies:
None of the failing tests are in these files or import from them. |
|
We audited this comprehensively on our end. The type guards are placed exactly where they need to be, handling both malformed objects and null entries correctly across both the estimator and pruner pipelines. The regression tests cover the crash edge cases perfectly. Fully support merging this to kill the crash loop! ✅ |
adca280 to
87d6b79
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87d6b79b55
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟠 Potential DoS via unbounded JSON.stringify of malformed tool-result text blocks
Description
Vulnerable code: function serializeMalformedTextBlock(block: unknown): string {
try {
const serialized = JSON.stringify(block);
return typeof serialized === "string" ? serialized : "[malformed text block]";
} catch {
return "[malformed text block]";
}
}
function coerceTextBlock(block: unknown): string | null {
...
return typeof text === "string" ? text : serializeMalformedTextBlock(block);
}RecommendationAvoid serializing arbitrary malformed blocks without bounds. Recommended options (prefer 1 or 2):
function coerceTextBlock(block: unknown): string | null {
if (!block || typeof block !== "object") return null;
if ((block as any).type !== "text") return null;
const text = (block as any).text;
return typeof text === "string" ? text : "[malformed text block]";
}
import { safeStringify } from "./safe-stringify"; // implement depth/length caps
function serializeMalformedTextBlock(block: unknown): string {
return safeStringify(block, { maxDepth: 4, maxLength: 2048 }) ?? "[malformed text block]";
}
2. 🟡 Malformed toolResult text blocks are JSON-stringified and injected into pruned LLM context
DescriptionThe context pruner now attempts to recover from malformed This can lead to unintended disclosure and prompt-injection amplification:
Vulnerable code: function serializeMalformedTextBlock(block: unknown): string {
try {
const serialized = JSON.stringify(block);
return typeof serialized === "string" ? serialized : "[malformed text block]";
} catch {
return "[malformed text block]";
}
}
function coerceTextBlock(block: unknown): string | null {
...
const text = (block as { text?: unknown }).text;
return typeof text === "string" ? text : serializeMalformedTextBlock(block);
}RecommendationAvoid serializing arbitrary malformed blocks into LLM-visible context. Recommended approaches:
function coerceTextBlock(block: unknown): string | null {
if (!block || typeof block !== "object") return null;
if ((block as any).type !== "text") return null;
const text = (block as any).text;
return typeof text === "string" ? text : "[malformed text block removed]";
}
const safe = { type: (block as any).type };
return JSON.stringify(safe).slice(0, 256);Additionally, consider applying existing secret-redaction logic (if any) to any fallback text before inserting it into the model context. Analyzed PR: #51267 at commit Last updated on: 2026-04-25T06:32:29Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cfeebbcbc3
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| dirName: "google-gemini-cli-auth", | ||
| idHint: "google-gemini-cli-auth", | ||
| source: { | ||
| source: "./index.ts", | ||
| built: "index.js", |
There was a problem hiding this comment.
Remove nonexistent auth plugins from bundled metadata
collectBundledPluginMetadata() in scripts/generate-bundled-plugin-metadata.mjs only serializes extensions/* directories that still have both package.json and openclaw.plugin.json, and this commit contains neither extensions/google-gemini-cli-auth nor extensions/minimax-portal-auth. Because these snapshot entries were added anyway, regenerating src/plugins/bundled-plugin-metadata.generated.ts drops them again (I verified the collector returns 75 entries while the checked-in snapshot now has 77), so the matches the generated metadata snapshot check in src/plugins/bundled-plugin-metadata.test.ts will fail and CI will keep reporting the generated file as stale.
Useful? React with 👍 / 👎.
…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>
- Tighten test assertion from toBeGreaterThanOrEqual(0) to toBeGreaterThan(0) so the unknown-block fallback is actually enforced (greptile review). - Add null/undefined guards to all content block loops in pruner.ts to prevent TypeError on block.type when sessions contain null entries (codex review). - Add regression test for toolResult with null content entries in pruner.test.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
60af534 to
ca7d630
Compare
|
Maintainer update before landing:
Local verification:
Waiting for the fresh GitHub checks before merge. |
Three external-contributor commits from the last day landed without CHANGELOG entries: - Alex Fries (#68286, @ajfonthemove): hybrid memory search component scores. Added under Unreleased > Changes (feat). - Charles Dusek (#51267, @cgdusek): malformed tool-result text-block guard. Added under Unreleased > Fixes. - Jerome Benoit (#59935, @jerome-benoit): Nix Home Manager daemon PATH support. Added under Unreleased > Fixes. Also drop a duplicate raw-subject changelog line for #66884 that restated alexlomt's already-formatted entry one line above.
… 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.
Three external-contributor commits from the last day landed without CHANGELOG entries: - Alex Fries (openclaw#68286, @ajfonthemove): hybrid memory search component scores. Added under Unreleased > Changes (feat). - Charles Dusek (openclaw#51267, @cgdusek): malformed tool-result text-block guard. Added under Unreleased > Fixes. - Jerome Benoit (openclaw#59935, @jerome-benoit): Nix Home Manager daemon PATH support. Added under Unreleased > Fixes. Also drop a duplicate raw-subject changelog line for openclaw#66884 that restated alexlomt's already-formatted entry one line above.
… 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.
Three external-contributor commits from the last day landed without CHANGELOG entries: - Alex Fries (openclaw#68286, @ajfonthemove): hybrid memory search component scores. Added under Unreleased > Changes (feat). - Charles Dusek (openclaw#51267, @cgdusek): malformed tool-result text-block guard. Added under Unreleased > Fixes. - Jerome Benoit (openclaw#59935, @jerome-benoit): Nix Home Manager daemon PATH support. Added under Unreleased > Fixes. Also drop a duplicate raw-subject changelog line for openclaw#66884 that restated alexlomt's already-formatted entry one line above.
… 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.
Three external-contributor commits from the last day landed without CHANGELOG entries: - Alex Fries (openclaw#68286, @ajfonthemove): hybrid memory search component scores. Added under Unreleased > Changes (feat). - Charles Dusek (openclaw#51267, @cgdusek): malformed tool-result text-block guard. Added under Unreleased > Fixes. - Jerome Benoit (openclaw#59935, @jerome-benoit): Nix Home Manager daemon PATH support. Added under Unreleased > Fixes. Also drop a duplicate raw-subject changelog line for openclaw#66884 that restated alexlomt's already-formatted entry one line above.
… 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.
Three external-contributor commits from the last day landed without CHANGELOG entries: - Alex Fries (openclaw#68286, @ajfonthemove): hybrid memory search component scores. Added under Unreleased > Changes (feat). - Charles Dusek (openclaw#51267, @cgdusek): malformed tool-result text-block guard. Added under Unreleased > Fixes. - Jerome Benoit (openclaw#59935, @jerome-benoit): Nix Home Manager daemon PATH support. Added under Unreleased > Fixes. Also drop a duplicate raw-subject changelog line for openclaw#66884 that restated alexlomt's already-formatted entry one line above.
… 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.
Three external-contributor commits from the last day landed without CHANGELOG entries: - Alex Fries (openclaw#68286, @ajfonthemove): hybrid memory search component scores. Added under Unreleased > Changes (feat). - Charles Dusek (openclaw#51267, @cgdusek): malformed tool-result text-block guard. Added under Unreleased > Fixes. - Jerome Benoit (openclaw#59935, @jerome-benoit): Nix Home Manager daemon PATH support. Added under Unreleased > Fixes. Also drop a duplicate raw-subject changelog line for openclaw#66884 that restated alexlomt's already-formatted entry one line above.
… 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.
Summary
isTextBlocktype guard to requiretypeof block.text === "string"(not justblock.type === "text")block.type === "text"sites inpruner.ts(collectTextSegments,collectPrunableToolResultSegments,estimateTextAndImageChars){type: "text"}blocks in both the char estimator and context prunerA plugin tool handler returning
undefined/voidproduces a malformed content block{type: "text"}(notextproperty). This block persists to session JSONL and crashes the context truncation pipeline withTypeError: Cannot read properties of undefined (reading 'length')on every subsequent message, locking the session into an unrecoverable crash loop.Closes #34979
Test plan
pnpm test -- src/agents/pi-embedded-runner/tool-result-char-estimator.test.tspasses (4 new tests)pnpm test -- src/agents/pi-extensions/context-pruning/pruner.test.tspasses (2 new + 11 existing tests)Based on prior work by @alvinttang (#39331) and @coffeexcoin (#34980).
🤖 Generated with Claude Code