#4964: fix: strip null-valued optional parameters from tool calls for provider compatibility
agents
size: M
Cluster:
Error Handling in Agent Tools
## Summary
Some LLMs (e.g., certain configurations of DeepSeek, Llama) return explicit `null` values for optional tool call parameters instead of omitting them entirely. For example:
```json
{"message": "hello", "recipient": "Umut", "tags": null}
```
OpenClaw's AJV validation expects optional parameters to be omitted rather than set to `null`, causing validation failures like:
```
Validation failed: must NOT have additional properties
```
This PR adds a normalization step that strips null-valued properties from tool call arguments before validation.
## Changes
| File | Change |
|------|--------|
| `src/agents/tool-call-id.ts` | Added `normalizeToolCallArguments()` and `removeNullProperties()` functions |
| `src/agents/pi-embedded-helpers/images.ts` | Integrated normalization into message sanitization pipeline |
| `src/agents/tool-call-id.test.ts` | Added 10 new tests including real provider scenario |
| `CHANGELOG.md` | Added entry |
## How it works
- `removeNullProperties<T>()` recursively strips `null` and `undefined` values from objects
- `normalizeToolCallArguments()` processes assistant messages and cleans tool call arguments
- Handles `toolCall`, `toolUse`, and `functionCall` block types
- Preserves messages without arguments field (doesn't add empty `{}`)
**Before:** `{"tags": null}` → AJV validation error
**After:** `{"tags": null}` → `{}` → validation passes
## Test plan
- [x] Unit tests for `normalizeToolCallArguments` (10 new tests)
- [x] Real provider scenario test with actual LLM output
- [x] All 192 tests passing
- [x] Lint passing
- [x] Build passing
🤖 Built with Claude
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR adds a normalization pass for assistant tool-call blocks to improve cross-provider compatibility when models emit explicit `null`/`undefined` for optional tool parameters. It introduces `normalizeToolCallArguments()` (used in the session message sanitization pipeline) plus unit tests covering common and “real provider” cases, and notes the fix in the changelog.
In the existing agents pipeline, tool-call IDs are already sanitized for provider constraints; this change extends that compatibility layer to tool-call *arguments* by stripping null-valued optionals before AJV/TypeBox validation.
<h3>Confidence Score: 4/5</h3>
- This PR is generally safe to merge; main behavior change is localized to tool-call argument normalization.
- Tests cover key scenarios and the change is scoped to assistant tool-call blocks, but the implementation currently treats any object as a plain object (prototype loss) and uses JSON.stringify-based change detection, which could have edge-case behavior/perf costs if non-JSON values ever appear in arguments.
- src/agents/tool-call-id.ts
<!-- 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
#11453: fix: apply normalizeToolName() at all tool lookup points
by sypsyp97 · 2026-02-07
80.2%
#19094: Fix empty tool_call_id and function names in provider transcript pa...
by yxshee · 2026-02-17
80.1%
#12608: fix: sanitize client tool call IDs per provider requirements
by piyushhhxyz · 2026-02-09
79.7%
#19024: fix: Fix normalise toolid
by chetaniitbhilai · 2026-02-17
78.8%
#19394: fix(agents): normalize tool call arguments dropped to {} (#19261)
by DevvGwardo · 2026-02-17
78.6%
#3647: fix: sanitize tool arguments in session history
by nhangen · 2026-01-29
77.8%
#14098: Sanitize JSON tool-call payload text
by helloember99 · 2026-02-11
77.5%
#21166: fix(agents): sanitize tool names in session transcript repair (#8595)
by dinakars777 · 2026-02-19
77.4%
#12812: fix(transcript-policy): sanitize tool call IDs for all non-OpenAI p...
by justin-nevins · 2026-02-09
77.3%
#12487: fix(agents): strip orphaned tool_result when tool_use is sanitized ...
by skylarkoo7 · 2026-02-09
77.0%