← Back to PRs

#23648: fix: detect truncated file paths from partial JSON streaming

by davidemanuelDEV open 2026-02-22 15:05 View on GitHub →
agents size: S
## Problem Fixes #23622 When a model calls the `edit` tool with a relatively long file path, the streaming JSON accumulation can silently truncate string values. The `partial-json` library used by `parseStreamingJson` closes unterminated strings on the `done` boundary, producing paths like `README.m` instead of `README.md`. This causes confusing "file not found" errors (or JSON parse errors when the truncation includes leaked JSON structure like trailing commas). ## Root Cause The upstream `pi-ai` streaming layer uses `partial-json` to parse incomplete JSON during streaming. This is correct for progressive display during streaming, but on the final `done`/`content_block_stop` event, the same forgiving parser is used — silently truncating values instead of reporting corruption. ## Fix Add a `detectTruncatedPath` heuristic in OpenClaw's tool wrapper layer that catches: - **Single-character extensions** that aren't known valid ones (`.c`, `.h`, `.r`, `.d`, `.v`, `.o`, `.a`, `.s`, `.S`, `.R`) — e.g. `.m` (truncated `.md`), `.t` (truncated `.ts`), `.j` (truncated `.js`) - **Leaked JSON structure** — trailing commas/spaces from partial parsing (e.g. `README.m, `) When detected, throws a descriptive `parameterValidationError` that tells the model the path appears truncated and to retry with the complete path. This is applied to all file-based tool wrappers: - `wrapToolParamNormalization` (edit, write) - `wrapToolWorkspaceRootGuard` (sandboxed tools) - `createOpenClawReadTool` (read) ## Testing Added 29 unit tests covering: - Valid paths with multi-char extensions (not flagged) - Legitimate single-char extensions like `.c`, `.h`, `.R` (not flagged) - Truncated paths with suspicious single-char extensions (flagged) - Paths with leaked JSON commas (flagged) - Edge cases (empty strings, dotfiles, no extension) ``` ✓ src/agents/pi-tools.truncated-path-detection.test.ts (29 tests) 3ms ``` ## Note The ideal long-term fix is in the `pi-ai` streaming layer: use strict `JSON.parse` on the `done` event and only fall back to `partial-json` during streaming deltas. This PR provides a defensive check at the OpenClaw layer that catches the symptom regardless of the upstream fix timeline. --- *This PR was AI-assisted (per CONTRIBUTING.md guidelines).* <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a defensive heuristic (`detectTruncatedPath`) to catch file paths silently truncated by the `partial-json` library during streaming tool call argument parsing, throwing a descriptive retry error instead of a confusing "file not found." The guard is applied at all three tool wrapper entry points (`wrapToolParamNormalization`, `wrapToolWorkspaceRootGuard`, `createOpenClawReadTool`). - **Missing `.m` extension in allowlist**: The `VALID_SINGLE_CHAR_EXTENSIONS` set omits `.m` (Objective-C / MATLAB), a widely-used extension — especially relevant since this project includes iOS/macOS apps. This will cause false positive truncation errors when users work with `.m` files. - **Test file duplicates production code**: The test suite copies the `detectTruncatedPath` function and allowlist instead of importing them, meaning tests validate a separate copy rather than the actual production logic. Future drift between the two would go undetected. <h3>Confidence Score: 3/5</h3> - The heuristic approach is sound but has a false positive gap for Objective-C `.m` files that should be fixed before merging. - The core idea is good and well-placed in the code, but the missing `.m` extension in the allowlist will cause false positives for a common file type. The test duplication issue reduces confidence that the tests will catch regressions in the actual production code. - `src/agents/pi-tools.read.ts` (missing `.m` in `VALID_SINGLE_CHAR_EXTENSIONS`), `src/agents/pi-tools.truncated-path-detection.test.ts` (tests don't import production code) <sub>Last reviewed commit: 5f9bd56</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs