﻿# A tool call truncated by max_tokens is discarded without a record

- **Status**: Todo
- **Kind**: Bug
- **Authors**: jp
- **Date**: 2026-09-07
- **Label**: domain=llm
- **Label**: package=jp_llm
- **Label**: type=bug

When a response runs out of output tokens while the model is still assembling a
tool call, the partial call is dropped and nothing durable records that it
existed.

`TurnCoordinator::handle_streaming_event`
(`crates/jp_cli/src/cmd/query/turn/coordinator.rs:322-324`) collects the names
via `event_builder.incomplete_tool_calls()`, with the comment "Capture tool-call
buffers about to be discarded", purely so `finish_notice` can name them in a
chrome line.
The call is never executed and never persisted.

The reason itself is dropped too: `transition_from_streaming` takes it as
`_reason` (`coordinator.rs:364-367`) and ignores it, and `FinishReason` does not
appear anywhere in `jp_conversation`.

So the surviving record is the prose the model got through, which reads as if
the assistant considered the action and chose not to take it.
Replaying the conversation later shows the same thing, because there is nothing
else to show.

## What it costs

A caller asked for an edit, got an answer, and exited 0.
The edit never happened and no artifact says so.
`jp conversation print` cannot show it, and neither can the macOS app, since
both read the stream.

## Proposal

Record the finish reason on the turn, and the discarded tool call names with it.
That gives the fact a second home, which is what a rare-but-consequential event
wants: the user is not watching for it, so a durable record they can find
afterwards serves them better than a line that scrolls past.

It also removes the reason `finish_notice` is currently the only witness to
anything, which keeps `--quiet` free of exemptions.

## Note on frequency

Rarer than it looks on Anthropic, where `chain_on_max_tokens` defaults to true
and `should_chain` (`crates/jp_llm/src/provider/anthropic.rs:447-451`) swallows
a `MaxTokens` finish to issue a continuation request.
Chaining is Anthropic-only, so `openai`, `google`, `cerebras`, `llamacpp`, and
`openrouter` reach this path normally, as does Anthropic once `max_tokens` is
set explicitly.

## Severity

Hidden.
The dropped action is invisible in stdout, in the exit status, and in the
conversation.
