fix: Mark shell-only assistant messages as done, not stuck busy #1
No reviewers
Labels
No labels
backend
bug
chore
duplication
effort:complex
effort:medium
effort:trivial
enhancement
follow-up
frontend
fullstack
priority:high
ready-for-agent
refactor
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
dries/ocman!1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "test123"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Sending a
!-prefixed shell command via the composer left the sessionstuck 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}/shellsynthesises an assistant message containing a single completed
bashtool part and no
finishfield (no LLM turn ran, so OpenCode neverrecords a stop reason).
InferSessionStatussawrole=assistantwithno
finishand noerrorand returned"busy"indefinitely. Thefrontend'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-startpart (no LLM turn started) and no part in arunningstate (no in-flight tool). When matched, report
doneinstead ofbusy. Real LLM turns are unaffected — they always emit astep-startbefore any other parts.The check runs in both status paths:
internal/db/sessions.go— added a correlated subquery to theGetSessionsSQL exposing alast_synth_terminalflag.internal/platforms/opencode/client.go— newisSynthesizedTerminalhelper for the live HTTP path; mirrors the SQL predicate over the
in-memory message parts.
internal/db/types.go—InferSessionStatusgains asynthesizedTerminal boolparameter that overrides thebusybranch when set and there is no finish/error.
Verification
go test ./...passes. New table cases cover: shell-onlysynthesised message, mid-flight LLM turn (
step-startpresent),in-flight tool (
state.status=running), and the empty-parts edgecase (still
busy).~/.local/share/opencode/opencode.db:five sessions flip from
busy→done(all genuineshell/patch-only synthesised messages), zero false positives on
real LLM turns.