You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Message IDs wrap around, silently dropping new user input in sessions with history #42639
In a session with existing message history, a new user message can be silently
ignored — the agent loop exits without processing it.
Root cause: Identifier.ascending() IDs are NOT monotonic. packages/opencode/src/id/id.ts
encodes Date.now()*0x1000 + counter into a 6-byte (48-bit) field:
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
const timeBytes = Buffer.alloc(6) // only 48 bits
Date.now()*0x1000 (~7.2e15 in 2026) exceeds 2^48 (~2.8e14) by ~25x, so the high
bits are truncated. A freshly generated "msg_000..." sorts before older IDs.
MessageV2.latest() picks the latest message by info.id > user.id (string
compare), assuming IDs are monotonic (its comment says so). After wraparound it
returns a stale message; the run loop then sees lastUser.id < lastAssistant.id
and exits, dropping the new input.
Suggested fix: compare by info.time.created (a real ms timestamp) with id as
tiebreaker, matching the existing (desc time_created, desc id) pagination
ordering.
Start a session and exchange a few messages (build up message history).
Wait until the 48-bit timestamp field wraps — current date (2026) is already
in the wrap window, since Date.now()*0x1000 overflows 2^48.
Send a new user message in the SAME session.
Observe: the agent does not respond. The run loop logs "exiting loop" and
the new input is never sent to the model.
Reproduction is time-dependent (tied to the wraparound point of the 48-bit
timestamp), so it is intermittent rather than on every message.
A reliable way to reproduce without waiting: unit-test latest() with messages
whose time.created increases but whose ids wrap (a fresh "msg_000..." after
older "msg_0a8a...").
This issue might be a duplicate of existing issues. Please check:
[Bug] Agent loop produces duplicate responses due to non-monotonic MessageID comparison #38787: Same root cause — non-monotonic MessageID string comparison in the agent run loop. That issue also identifies lastUser.id < lastAssistant.id as the broken exit condition and suggests comparing by time.created instead. The symptom differs (duplicate responses vs. dropped input) but the underlying bug and fix are the same.
Additional confirmed reproduction + evidence from a real install (Windows, opencode desktop bundle + CLI):
Wrap point measured precisely: the 6-byte field rolls over at 2026-08-14 11:23:02 UTC on this machine. A message at 08-13 15:28 UTC gets ID prefix FFBBCCE2D000 (msg_ffbbcce2d001uUMvHYqYBDb6Cp) while messages a few hours later start with 000E.../0039.... So latest() picks the stale ffb... assistant as the "latest".
Concrete sorted-vs-time mismatch in the affected session:
chronologically last message: msg_003a81e41001ylDQOsSbNrB8ZX (user)
lexicographically max message: msg_ffbbcce2d001uUMvHYqYBDb6Cp (assistant, finish=stop)
→ loop condition lastUser.id < lastAssistant.id is true and the loop breaks with only loop step=0 -> exiting loop in the log; no process/stream ever runs.
Affects any session spanning the wrap point, regardless of model/provider. In our case the stuck session had messages with 4 different models (deepseek-v4-flash-free, qwen, minimax, local) - all silently dropped. Confirms it is not provider/model related.
CLI reproduces it too (not desktop-UI only): opencode run --session <id> ... --print-logs on the same DB logs loop step=0 then exiting loop immediately.
Workaround for users until merged: sessions created entirely after the wrap time (all IDs monotonic) keep working; for stuck sessions, --fork into a new session works.
FWIW the bundled desktop code path is latest() selecting by info.id > user.id (string compare) in packages/opencode/src/session/message-v2.ts, exactly as described. The proposed fix (order by time.created with id tiebreaker) is correct; id.ts should also keep more than 6 bytes (or use a proper monotonic ULID) so IDs stay sortable across wraps.
Activity
github-actions commentedon Aug 14, 2026
This issue might be a duplicate of existing issues. Please check:
MessageIDstring comparison in the agent run loop. That issue also identifieslastUser.id < lastAssistant.idas the broken exit condition and suggests comparing bytime.createdinstead. The symptom differs (duplicate responses vs. dropped input) but the underlying bug and fix are the same.sungch888 commentedon Aug 15, 2026
Additional confirmed reproduction + evidence from a real install (Windows, opencode desktop bundle + CLI):
Wrap point measured precisely: the 6-byte field rolls over at
2026-08-14 11:23:02 UTCon this machine. A message at08-13 15:28 UTCgets ID prefixFFBBCCE2D000(msg_ffbbcce2d001uUMvHYqYBDb6Cp) while messages a few hours later start with000E.../0039.... Solatest()picks the staleffb...assistant as the "latest".Concrete sorted-vs-time mismatch in the affected session:
msg_003a81e41001ylDQOsSbNrB8ZX(user)msg_ffbbcce2d001uUMvHYqYBDb6Cp(assistant,finish=stop)→ loop condition
lastUser.id < lastAssistant.idis true and the loopbreaks with onlyloop step=0 -> exiting loopin the log; noprocess/streamever runs.Affects any session spanning the wrap point, regardless of model/provider. In our case the stuck session had messages with 4 different models (deepseek-v4-flash-free, qwen, minimax, local) - all silently dropped. Confirms it is not provider/model related.
CLI reproduces it too (not desktop-UI only):
opencode run --session <id> ... --print-logson the same DB logsloop step=0thenexiting loopimmediately.Workaround for users until merged: sessions created entirely after the wrap time (all IDs monotonic) keep working; for stuck sessions,
--forkinto a new session works.FWIW the bundled desktop code path is
latest()selecting byinfo.id > user.id(string compare) inpackages/opencode/src/session/message-v2.ts, exactly as described. The proposed fix (order bytime.createdwith id tiebreaker) is correct;id.tsshould also keep more than 6 bytes (or use a proper monotonic ULID) so IDs stay sortable across wraps.ChananM commentedon Aug 16, 2026
This was fixed in db581e47a3a6f4900a6289ad7fddec60fec44e1c
rekram1-node commentedon Aug 17, 2026
If u update to latest this was fixed