fix(cli): align ingest step counter with SDK num_turns

The Claude Code runtime counted every SDKAssistantMessage with
parent_tool_use_id === null as a step, but the SDK emits extra messages
within a single num_turns round-trip — `stop_reason: 'pause_turn'`
continuations and errored partials it retries internally. The local
counter then outran maxTurns and the ingest HUD rendered confusing
ratios like `step 69/40`.

Filter both cases in collectResult so stepIndex tracks num_turns and
stays bounded by the work-unit stepBudget.
This commit is contained in:
Andrey Avtomonov 2026-05-28 01:55:39 +02:00
parent a94f35800a
commit 20454bcb8f
3 changed files with 77 additions and 3 deletions

View file

@ -58,6 +58,22 @@ function isResult(message: SDKMessage): message is SDKResultMessage {
return message.type === 'result';
}
// Skip emissions the SDK does not count toward `num_turns`: `pause_turn` continuations and
// errored partials (e.g. `max_output_tokens`) it retries internally. Without this, the
// runtime's step counter outruns `maxTurns` and the HUD renders e.g. `step 69/40`.
function countsAsAssistantTurn(message: SDKMessage): boolean {
if (message.type !== 'assistant' || message.parent_tool_use_id !== null) {
return false;
}
if (message.error !== undefined) {
return false;
}
if (message.message.stop_reason === 'pause_turn') {
return false;
}
return true;
}
function resultError(result: SDKResultMessage): Error | undefined {
if (result.subtype === 'success') {
return undefined;
@ -190,7 +206,7 @@ async function collectResult(params: {
let result: SDKResultMessage | undefined;
for await (const message of params.query({ prompt: params.prompt, options: params.options })) {
assertInitIsolation(message, params.allowedToolIds, params.expectedMcpServerNames);
if (message.type === 'assistant' && message.parent_tool_use_id === null) {
if (countsAsAssistantTurn(message)) {
await params.onAssistantTurn?.();
}
if (isResult(message)) {