# Hide synthetic "missing tool result" repair messages from assistant context

**OpenClaw version:** 2026.6.8
**Component:** `dist/session-transcript-repair-*.js` + Control-UI history filter
**Severity:** UX / context-pollution (no crash)

## What happens

When a turn is interrupted (user sends a new message while tool calls are still in flight, stream is aborted, or a runtime crash kills the run mid-tool-use), the next request to the model provider would fail because an Anthropic-style `tool_use` block has no matching `tool_result`.

`session-transcript-repair-*.js` correctly catches this and injects a synthetic `toolResult` with:

```
[openclaw] missing tool result in session history; inserted synthetic error result for transcript repair.
```

This keeps the transcript replayable — good. **But the synthetic result is then visible to the assistant on the next turn**, and on every following turn, polluting the model's context with a hardcoded error string for every interrupted tool call.

Real-world effect: when the user interrupts a parallel tool batch (e.g. 4 tool calls running, user sends "stop" or a follow-up), the assistant sees a cascade of synthetic errors and often re-narrates / apologizes for them instead of continuing with the user's actual intent.

## Root cause

`isSyntheticMissingToolResult(msg)` already exists in `session-transcript-repair-*.js` (line ~95 in the bundled file) and correctly identifies these messages via the `details["opencl…sult"]` marker. However, this predicate is **not** wired into the Control-UI history filter (`Eg` / `Dg` / `isHiddenAssistantMessage` in `control-ui/assets/index-*.js`), nor into the agent context-builder that constructs the message list sent to the model.

So the messages stay visible both to:
- the assistant on the next turn (model context)
- the human in the UI transcript

## Suggested fix

Two-layer fix, analogous to how `NO_REPLY` messages are already handled:

1. **Agent context builder:** When constructing the message list for the next model call, filter out `toolResult` messages where `isSyntheticMissingToolResult(msg) === true` **AND** the assistant turn that originally referenced this `tool_use_id` is also filtered/replaced — so the pair stays consistent. Alternative: keep the synthetic result but rewrite its content to an empty string or a single token like `"[interrupted]"` so it costs near-zero context.

2. **Control-UI:** Extend `Dg(e)` (the hidden-message predicate at `dist/control-ui/assets/index-Wjxp3gyC.js`, currently `Eg(e) || xg(e) || Cg(e)`) so the synthetic results are visually collapsed into a single inline marker ("⚠️ 3 interrupted tool calls") instead of multiple long error lines. `xg(e)` already exists for this check — just needs to be hooked into the visibility/render path.

## Reproduction

1. Send a message that triggers parallel tool calls (4+ tools in one block)
2. While tools are still running, send a second message ("stop" / "actually do X instead")
3. Observe the next assistant turn: it starts with multiple synthetic error blocks instead of cleanly handling the redirect

## Why this matters

Heavy users with parallel tool blocks (browser automation, multi-search, image-gen + research in one turn) hit this multiple times per day. Each occurrence wastes context tokens and confuses the assistant into apology loops.

## References

- Repair logic: `dist/session-transcript-repair-B9X9dB3H.js` lines 75–98
- Marker key: `SYNTHETIC_MISSING_TOOL_RESULT_DETAIL_KEY = "opencl…sult"`
- Existing UI predicate (unused for filtering): `xg(e)` in bundled Control-UI

---

Happy to PR if there's interest. Workflow-fix in user-land is "don't interrupt tool batches" which is fine for me but bad UX for new users.
