Session Loop Termination Condition Fails Causing Infinite Loop #9320

Open
opened 2026-02-16 18:12:10 -05:00 by yindo · 1 comment
Owner

Originally created by @15383644138 on GitHub (Feb 14, 2026).

Originally assigned to: @rekram1-node on GitHub.

Description

Under certain conditions, opencode sessions fail to terminate correctly, causing the assistant to continuously create new messages in an infinite loop, even when the model has returned a finish=stop status.

Plugins

No response

OpenCode version

1.2.1

Steps to reproduce

Bug Report: Session Loop Termination Condition Fails Causing Infinite Loop

Summary

Under certain conditions, opencode sessions fail to terminate correctly, causing the assistant to continuously create new messages in an infinite loop, even when the model has returned a finish=stop status.

Environment

  • opencode version: 1.2.1
  • Model: glm-5
  • OS: Linux
  • Session ID: ses_3a477417effeooCd5Cq2mJV3lE

Problem Description

After executing a security analysis task, the model returned finish=stop multiple times, but the system did not correctly terminate the loop. Instead, it continued creating new assistant messages, eventually producing 96 messages until the user manually aborted.

Key Metrics

Metric Value
User messages 2
Assistant messages 94
Assistant message finish values 88 with stop, 6 with tool-calls
Final state MessageAbortedError (user manual abort)
Accumulated input tokens 44,000+

Root Cause Analysis

The issue is in the loop termination condition at lines 263-271 of packages/opencode/src/session/prompt.ts:

if (
  lastAssistant?.finish &&
  !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
  lastUser.id < lastAssistant.id
) {
  log.info("exiting loop", { sessionID })
  break
}

The Problem

The condition lastUser.id < lastAssistant.id uses string comparison to determine message ordering.

Actual message IDs:

  • User message: msg_e6f0bd865e63
  • Assistant message: msg_c5b88be94001tLa8xeuxsiub6h

String comparison:

  • msg_e... vs msg_c...
  • Second character: e (ASCII 101) vs c (ASCII 99)
  • Result: "e" > "c"lastUser.id < lastAssistant.id = false

Loop Flow

while (true) {
  1. Get message stream
  2. lastUser = first user message (msg_e6f0bd865e63)
  3. lastAssistant = last assistant message (finish=stop)
  4. Check termination: lastUser.id < lastAssistant.id → false
  5. Condition not met, continue loop
  6. Create new assistant message
  7. Model returns finish=stop
  8. Back to step 1... (infinite loop)
}

Steps to Reproduce

  1. Start opencode serve
  2. Send a complex task that triggers multiple tool calls (e.g., using a skill for security analysis)
  3. After task completion, model returns finish=stop
  4. Observe whether the session terminates correctly

Expected Behavior

When the model returns finish=stop, the session should correctly terminate the loop and stop creating new assistant messages.

Actual Behavior

The session continues creating new assistant messages in an infinite loop until:

  • User manually aborts
  • Or another error is triggered

Impact

  1. Resource consumption: CPU, memory, and API calls continuously grow
  2. Cost increase: Unnecessary model API calls
  3. User experience: Session hangs and cannot end normally

Suggested Fixes

Option 1: Remove ID Comparison (Recommended)

Since the finish status already indicates the model has completed:

if (
  lastAssistant?.finish &&
  !["tool-calls", "unknown"].includes(lastAssistant.finish)
) {
  log.info("exiting loop", { sessionID })
  break
}

Option 2: Use Timestamp Comparison

if (
  lastAssistant?.finish &&
  !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
  lastUser.time.created < lastAssistant.time.created
) {
  log.info("exiting loop", { sessionID })
  break
}

Option 3: Ensure IDs are Comparable

If ID comparison is necessary, ensure the ID generation strategy produces lexicographically correct identifiers (e.g., using timestamp prefixes).

Relevant Logs

INFO  session.prompt step=0 sessionID=ses_3a477417effeooCd5Cq2mJV3lE loop
INFO  session.prompt step=1 sessionID=ses_3a477417effeooCd5Cq2mJV3lE loop
...
INFO  session.prompt step=88 sessionID=ses_3a477417effeooCd5Cq2mJV3lE loop
// "exiting loop" log never appears

Database Verification

-- Verify message IDs and finish status
SELECT id, json_extract(data, '$.role'), json_extract(data, '$.finish') 
FROM message 
WHERE session_id='ses_3a477417effeooCd5Cq2mJV3lE' 
ORDER BY time_created;

-- Results show:
-- User message ID: msg_e6f0bd865e63 (finish: null)
-- Assistant message ID: msg_c5b88be94001tLa8xeuxsiub6h (finish: stop)
-- String comparison: "e" > "c", causing the condition to always be false

Related Files

  • packages/opencode/src/session/prompt.ts - Loop termination logic
  • packages/opencode/src/session/processor.ts - Finish status setting
  • packages/opencode/src/id/id.ts - ID generation strategy

Report Date: 2026-02-14
Reporter: opencode user fsdgGFF

Screenshot and/or share link

No response

Operating System

linux

Terminal

No response

Originally created by @15383644138 on GitHub (Feb 14, 2026). Originally assigned to: @rekram1-node on GitHub. ### Description Under certain conditions, opencode sessions fail to terminate correctly, causing the assistant to continuously create new messages in an infinite loop, even when the model has returned a `finish=stop` status. ### Plugins _No response_ ### OpenCode version 1.2.1 ### Steps to reproduce # Bug Report: Session Loop Termination Condition Fails Causing Infinite Loop ## Summary Under certain conditions, opencode sessions fail to terminate correctly, causing the assistant to continuously create new messages in an infinite loop, even when the model has returned a `finish=stop` status. ## Environment - **opencode version**: 1.2.1 - **Model**: glm-5 - **OS**: Linux - **Session ID**: ses_3a477417effeooCd5Cq2mJV3lE ## Problem Description After executing a security analysis task, the model returned `finish=stop` multiple times, but the system did not correctly terminate the loop. Instead, it continued creating new assistant messages, eventually producing 96 messages until the user manually aborted. ### Key Metrics | Metric | Value | |--------|-------| | User messages | 2 | | Assistant messages | 94 | | Assistant message finish values | 88 with `stop`, 6 with `tool-calls` | | Final state | MessageAbortedError (user manual abort) | | Accumulated input tokens | 44,000+ | ## Root Cause Analysis The issue is in the loop termination condition at lines 263-271 of `packages/opencode/src/session/prompt.ts`: ```typescript if ( lastAssistant?.finish && !["tool-calls", "unknown"].includes(lastAssistant.finish) && lastUser.id < lastAssistant.id ) { log.info("exiting loop", { sessionID }) break } ``` ### The Problem The condition `lastUser.id < lastAssistant.id` uses **string comparison** to determine message ordering. Actual message IDs: - User message: `msg_e6f0bd865e63` - Assistant message: `msg_c5b88be94001tLa8xeuxsiub6h` String comparison: - `msg_e...` vs `msg_c...` - Second character: `e` (ASCII 101) vs `c` (ASCII 99) - Result: `"e" > "c"` → `lastUser.id < lastAssistant.id` = **false** ### Loop Flow ``` while (true) { 1. Get message stream 2. lastUser = first user message (msg_e6f0bd865e63) 3. lastAssistant = last assistant message (finish=stop) 4. Check termination: lastUser.id < lastAssistant.id → false 5. Condition not met, continue loop 6. Create new assistant message 7. Model returns finish=stop 8. Back to step 1... (infinite loop) } ``` ## Steps to Reproduce 1. Start opencode serve 2. Send a complex task that triggers multiple tool calls (e.g., using a skill for security analysis) 3. After task completion, model returns `finish=stop` 4. Observe whether the session terminates correctly ## Expected Behavior When the model returns `finish=stop`, the session should correctly terminate the loop and stop creating new assistant messages. ## Actual Behavior The session continues creating new assistant messages in an infinite loop until: - User manually aborts - Or another error is triggered ## Impact 1. **Resource consumption**: CPU, memory, and API calls continuously grow 2. **Cost increase**: Unnecessary model API calls 3. **User experience**: Session hangs and cannot end normally ## Suggested Fixes ### Option 1: Remove ID Comparison (Recommended) Since the `finish` status already indicates the model has completed: ```typescript if ( lastAssistant?.finish && !["tool-calls", "unknown"].includes(lastAssistant.finish) ) { log.info("exiting loop", { sessionID }) break } ``` ### Option 2: Use Timestamp Comparison ```typescript if ( lastAssistant?.finish && !["tool-calls", "unknown"].includes(lastAssistant.finish) && lastUser.time.created < lastAssistant.time.created ) { log.info("exiting loop", { sessionID }) break } ``` ### Option 3: Ensure IDs are Comparable If ID comparison is necessary, ensure the ID generation strategy produces lexicographically correct identifiers (e.g., using timestamp prefixes). ## Relevant Logs ``` INFO session.prompt step=0 sessionID=ses_3a477417effeooCd5Cq2mJV3lE loop INFO session.prompt step=1 sessionID=ses_3a477417effeooCd5Cq2mJV3lE loop ... INFO session.prompt step=88 sessionID=ses_3a477417effeooCd5Cq2mJV3lE loop // "exiting loop" log never appears ``` ## Database Verification ```sql -- Verify message IDs and finish status SELECT id, json_extract(data, '$.role'), json_extract(data, '$.finish') FROM message WHERE session_id='ses_3a477417effeooCd5Cq2mJV3lE' ORDER BY time_created; -- Results show: -- User message ID: msg_e6f0bd865e63 (finish: null) -- Assistant message ID: msg_c5b88be94001tLa8xeuxsiub6h (finish: stop) -- String comparison: "e" > "c", causing the condition to always be false ``` ## Related Files - `packages/opencode/src/session/prompt.ts` - Loop termination logic - `packages/opencode/src/session/processor.ts` - Finish status setting - `packages/opencode/src/id/id.ts` - ID generation strategy --- **Report Date**: 2026-02-14 **Reporter**: opencode user fsdgGFF ### Screenshot and/or share link _No response_ ### Operating System linux ### Terminal _No response_
yindo added the bugperf labels 2026-02-16 18:12:10 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Feb 14, 2026):


This issue appears to be a duplicate of existing reports. Please check:

  • #11863: "Web UI: client-generated message IDs cause infinite processing loop when client/server clocks diverge" - Identifies the exact same root cause: the string comparison issue with message IDs in session/prompt.ts, showing how lastUser.id < lastAssistant.id fails due to comparing "e" > "c"
  • #11153: "Session loop doesn't stop when model returns finish_reason=stop" - Reports the same symptom of infinite loops when the model indicates completion

#11863 in particular investigates the same bug from the Web UI angle and contains the exact logic breakdown you've documented here.

@github-actions[bot] commented on GitHub (Feb 14, 2026): --- This issue appears to be a duplicate of existing reports. Please check: - **#11863**: "Web UI: client-generated message IDs cause infinite processing loop when client/server clocks diverge" - Identifies the **exact same root cause**: the string comparison issue with message IDs in `session/prompt.ts`, showing how `lastUser.id < lastAssistant.id` fails due to comparing "e" > "c" - **#11153**: "Session loop doesn't stop when model returns finish_reason=stop" - Reports the same symptom of infinite loops when the model indicates completion #11863 in particular investigates the same bug from the Web UI angle and contains the exact logic breakdown you've documented here.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#9320