#15379: fix(antigravity): strip unsigned thinking blocks in agent loop via transformContext
agents
size: S
Cluster:
Tool Call ID Sanitization
## Summary
Fixes #13826
Google Antigravity returns Claude thinking blocks without `signature` fields. The existing `sanitizeAntigravityThinkingBlocks` in `sanitizeSessionHistory` only runs once per user turn (at session load), but within the same turn, pi-ai's `agent-loop.js` accumulates assistant responses—with unsigned thinking blocks—in `context.messages` and replays them on subsequent API calls during tool-call iterations. This causes:
```
LLM request rejected: messages.N.content.0.thinking.signature: Field required
```
### Fix
Use the `Agent.transformContext` hook (called by the agent loop before every API call) to run `sanitizeAntigravityThinkingBlocks`, stripping unsigned thinking blocks both at session replay **and** between tool-call iterations within a single turn.
The hook is only set when `transcriptPolicy.normalizeAntigravityThinkingBlocks` is true (i.e., Antigravity providers), so non-Antigravity providers are unaffected.
### Code flow (before fix)
```
agent-loop.js: streamAssistantResponse()
→ API returns assistant with thinking block (no signature)
→ stored in context.messages
agent-loop.js: tool execution → next streamAssistantResponse()
→ context.messages still has unsigned thinking blocks
→ API rejects: "thinking.signature: Field required"
```
### Code flow (after fix)
```
agent-loop.js: streamAssistantResponse()
→ config.transformContext(messages) ← strips unsigned thinking blocks
→ clean messages sent to API
```
## Test plan
- [x] All 81 `pi-embedded-runner` tests pass
- [x] `pnpm build && pnpm check` clean
- [x] Verified on live Antigravity deployment with tool-calling conversations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes a runtime error (`thinking.signature: Field required`) that occurs during multi-step tool-calling conversations with Google Antigravity Claude models. The root cause: unsigned thinking blocks accumulate in `context.messages` between tool-call iterations within the agent loop, and the existing `sanitizeSessionHistory` only runs once at session load. The fix hooks `sanitizeAntigravityThinkingBlocks` into the agent's `transformContext` callback (called before every API request), ensuring unsigned thinking blocks are stripped both at session replay and between tool-call iterations.
- Hooks `sanitizeAntigravityThinkingBlocks` via `Agent.transformContext` in `attempt.ts`, gated behind `transcriptPolicy.normalizeAntigravityThinkingBlocks` (only active for Antigravity Claude models)
- Includes a runtime reference-comparison guard to detect if the `transformContext` property assignment fails (e.g., due to `Object.freeze` or a read-only setter)
- Adds comprehensive unit tests for `sanitizeAntigravityThinkingBlocks` covering the `transformContext` code path (stripping unsigned blocks, preserving signed blocks, normalizing signature variants, dropping empty messages, reference identity)
- Adds transcript policy tests verifying `normalizeAntigravityThinkingBlocks` is enabled only for Antigravity Claude and not Gemini-via-Antigravity or direct Anthropic API
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — it's a targeted fix for a specific runtime error with good test coverage and minimal risk to non-Antigravity code paths.
- Score of 4 reflects a well-scoped fix with comprehensive tests and clear documentation. The only caveat is the reliance on an undocumented `transformContext` hook via type casting, but this is mitigated by the runtime guard and clear comments explaining the trade-off. The change is gated behind `normalizeAntigravityThinkingBlocks` so non-Antigravity providers are completely unaffected.
- `src/agents/pi-embedded-runner/run/attempt.ts` — the `transformContext` hook assignment relies on an undocumented upstream API; worth monitoring on pi-agent-core upgrades.
<sub>Last reviewed commit: a3a1680</sub>
<!-- greptile_other_comments_section -->
**Context used:**
- Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=fd949e91-5c3a-4ab5-90a1-cbe184fd6ce8))
- Context from `dashboard` - AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=0d0c8278-ef8e-4d6c-ab21-f5527e322f13))
<!-- /greptile_comment -->
Most Similar PRs
#18926: fix(agents): preserve thinking signatures for direct Anthropic API
by BinHPdev · 2026-02-17
83.1%
#16100: fix: convert unsigned thinking blocks to text to prevent signature ...
by claw-sylphx · 2026-02-14
81.8%
#19407: fix(agents): strip thinking blocks on cross-provider model switch (...
by lailoo · 2026-02-17
80.1%
#8345: fix: prevent synthetic error repair from creating tool_result for d...
by vishaltandale00 · 2026-02-03
80.1%
#13361: fix(google-antigravity): add Opus 4.6 support and fix thinking.sign...
by SovranAMR · 2026-02-10
79.1%
#4009: fix(agent): sanitize messages after orphan user repair
by drag88 · 2026-01-29
78.7%
#13831: fix(agents): include Anthropic in tool call ID sanitization
by lailoo · 2026-02-11
78.6%
#2806: [AI-Assisted] Fix: Repair tool_use/tool_result pairing for Claude o...
by Arthur742Ramos · 2026-01-27
78.2%
#20050: fix: Telegram polling regression and thinking blocks corruption (AI...
by Vaibhavee89 · 2026-02-18
77.8%
#22270: fix: add auto-recovery for thinking block immutability errors
by MunemHashmi · 2026-02-20
77.7%