fix: Mark shell-only assistant messages as done, not stuck busy #1

Merged
dries merged 1 commit from test123 into main 2026-05-03 14:24:56 +02:00
Owner

Problem

Sending a !-prefixed shell command via the composer left the session
stuck on the "Working" indicator forever, and any user message typed
afterwards rendered with a misleading "Queued" badge — even when the
LLM was actively responding to it.

Both symptoms shared one root cause: POST /session/{id}/shell
synthesises an assistant message containing a single completed bash
tool part and no finish field (no LLM turn ran, so OpenCode never
records a stop reason). InferSessionStatus saw role=assistant with
no finish and no error and returned "busy" indefinitely. The
frontend's queued-badge predicate reads the same field, so every
follow-up user message inherited the misclassification.

Fix

Detect the "synthesised terminal" pattern at status-derivation time:
the most recent assistant message has at least one part, no
step-start part (no LLM turn started) and no part in a running
state (no in-flight tool). When matched, report done instead of
busy. Real LLM turns are unaffected — they always emit a
step-start before any other parts.

The check runs in both status paths:

  • internal/db/sessions.go — added a correlated subquery to the
    GetSessions SQL exposing a last_synth_terminal flag.
  • internal/platforms/opencode/client.go — new isSynthesizedTerminal
    helper for the live HTTP path; mirrors the SQL predicate over the
    in-memory message parts.
  • internal/db/types.go — InferSessionStatus gains a
    synthesizedTerminal bool parameter that overrides the busy
    branch when set and there is no finish/error.

Verification

  • go test ./... passes. New table cases cover: shell-only
    synthesised message, mid-flight LLM turn (step-start present),
    in-flight tool (state.status=running), and the empty-parts edge
    case (still busy).
  • Ran the new SQL against ~/.local/share/opencode/opencode.db:
    five sessions flip from busy → done (all genuine
    shell/patch-only synthesised messages), zero false positives on
    real LLM turns.
## Problem Sending a `!`-prefixed shell command via the composer left the session stuck on the "Working" indicator forever, and any user message typed afterwards rendered with a misleading "Queued" badge — even when the LLM was actively responding to it. Both symptoms shared one root cause: `POST /session/{id}/shell` synthesises an assistant message containing a single completed `bash` tool part and no `finish` field (no LLM turn ran, so OpenCode never records a stop reason). `InferSessionStatus` saw `role=assistant` with no `finish` and no `error` and returned `"busy"` indefinitely. The frontend's queued-badge predicate reads the same field, so every follow-up user message inherited the misclassification. ## Fix Detect the "synthesised terminal" pattern at status-derivation time: the most recent assistant message has at least one part, no `step-start` part (no LLM turn started) and no part in a `running` state (no in-flight tool). When matched, report `done` instead of `busy`. Real LLM turns are unaffected — they always emit a `step-start` before any other parts. The check runs in both status paths: - `internal/db/sessions.go` — added a correlated subquery to the `GetSessions` SQL exposing a `last_synth_terminal` flag. - `internal/platforms/opencode/client.go` — new `isSynthesizedTerminal` helper for the live HTTP path; mirrors the SQL predicate over the in-memory message parts. - `internal/db/types.go` — `InferSessionStatus` gains a `synthesizedTerminal bool` parameter that overrides the `busy` branch when set and there is no finish/error. ## Verification - `go test ./...` passes. New table cases cover: shell-only synthesised message, mid-flight LLM turn (`step-start` present), in-flight tool (`state.status=running`), and the empty-parts edge case (still `busy`). - Ran the new SQL against `~/.local/share/opencode/opencode.db`: five sessions flip from `busy` → `done` (all genuine shell/patch-only synthesised messages), zero false positives on real LLM turns.
fix: Mark shell-only assistant messages as done, not stuck busy
Some checks failed
CI / Frontend (pull_request) Successful in 46s
CI / Backend (pull_request) Successful in 1m1s
CI / Playwright E2E (pull_request) Failing after 2m47s
CI / Build (pull_request) Successful in 1m12s
CI / Frontend (push) Successful in 47s
CI / Backend (push) Successful in 1m0s
CI / Playwright E2E (push) Failing after 2m50s
Semantic Tag / tag (push) Successful in 3s
CI / Build (push) Successful in 1m11s
d14d623b6e
dries merged commit d14d623b6e into main 2026-05-03 14:24:56 +02:00
dries deleted branch test123 2026-05-03 14:24:57 +02:00
Sign in to join this conversation.
No description provided.