From eb77fbc13b382eb46c5158165124c6e015449a21 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 14:55:24 +0000 Subject: [PATCH] fix(cli): prevent agent loop hang when stream ends without stop_reason Some Anthropic-compatible providers terminate the response stream with a final message_delta that carries stop_reason: null followed immediately by message_stop. The AI SDK then never sets a finishReason on the assistant message, the prompt loop's break check stays false, and the agent keeps issuing new model calls indefinitely. Default the missing finish to "unknown" so the existing exit condition fires when no tool calls were emitted. --- .changeset/sturdy-finish-guard.md | 5 +++++ packages/opencode/src/session/prompt.ts | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 .changeset/sturdy-finish-guard.md diff --git a/.changeset/sturdy-finish-guard.md b/.changeset/sturdy-finish-guard.md new file mode 100644 index 00000000000..c308018de97 --- /dev/null +++ b/.changeset/sturdy-finish-guard.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Prevent an infinite agent loop when a provider ends the response stream without a terminal stop reason. diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f8dd0dbd2fe..967db90dcb0 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1600,6 +1600,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the toolChoice: format.type === "json_schema" ? "required" : undefined, }) + // kilocode_change start - guard against providers that end the stream + // without a terminal stop_reason (e.g. an Anthropic-style message_delta + // with stop_reason: null followed immediately by message_stop). Without + // a finishReason, the loop-exit check below sees a falsy `finish` and + // keeps stepping forever. Default to "unknown" so the regular break + // condition fires when there are no tool calls, while still allowing + // the loop to continue when tool calls were emitted. + handle.message.finish = handle.message.finish ?? "unknown" + // kilocode_change end + if (structured !== undefined) { handle.message.structured = structured handle.message.finish = handle.message.finish ?? "stop"