mirror of
https://github.com/open-webui/computer.git
synced 2026-11-05 01:17:24 +00:00
[GH-ISSUE #306] bug: ACP agents (cursor, cline, gemini, grok) never finish a turn; the reply only appears on stop #219
Labels
No labels
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
open-webui/computer#219
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Originally created by @ciberick on GitHub (Sep 24, 2026).
Original GitHub issue: https://github.com/open-webui/computer/issues/306
Summary
An ACP-backed agent can stream its output and then never finish the turn. The chat stays in its
"working" state indefinitely, and the reply only appears once stop is pressed.
Observed with the
cursoragent: the first tokens and the tool activity appear live, then the turnhangs. Pressing stop immediately renders the complete result.
codexis unaffected, because it doesnot go through the ACP adapter.
The cause is in
acp_event_stream, and it affects all four ACP agents: cursor, cline, gemini andgrok.
Environment
0.9.21(reproduced againstmain, currentlyf9d1d8c)cursor(agent) via the ACP adaptermain; the standalone reproduction runs the realacp_event_streamwith a stub client, so it needs no agent binary and no cptr dependencies.Steps to reproduce
cursoragent profile.Root cause
acp_event_streamwaits on an empty queue forever:and the adapters check for completion inside the loop body:
That
if prompt_task.done()is only reachable when another event arrives. If the agent's last eventlands before
session/promptresolves — which is what a normal turn looks like — the generatorparks on
events.get(),prompt_taskcompletes unnoticed, andAgentDoneis never yielded.It is a race, which is why it is intermittent: the turn completes only when the agent happens to
emit one more event after the prompt has already resolved.
Why the reply appears on stop
chat_taskaccumulates the reply in memory and flushes and persists it in exactly two places:AgentDone(cptr/utils/chat_task.py:1823), andCancelledErrorhandler (cptr/utils/chat_task.py:2777).Since
AgentDonenever arrives, cancelling is the only path that writes out what was alreadystreamed — which is precisely what pressing stop does.
Reproduction
This drives the real
acp_event_streamwith a stub client. Run it from the repository root; itneeds no dependencies, because
acp.pyimports only the standard library at module level.The second case is the one that works today, and it works only by accident.
Suggested fix
Bound the stream by the prompt task. Once the prompt has resolved, keep draining while events still
arrive and stop on the first quiet window — so a trailing event is not lost to wherever the poll
boundary happened to fall, and a long silent tool run mid-turn is not cut short, because the turn is
still live.
Cancelling a
Queue.geton timeout is safe: the item stays queued andgethands the wakeup to thenext getter, so no event is dropped between polls.
and, in each of the four adapters, pass the prompt task:
in
cptr/utils/agents/cursor.py,cline.py,gemini.pyandgrok.py. Their existingpost-completion drain block becomes redundant but stays harmless, so it can be left in place to keep
the change small.
Verified against the patched module, with the reproduction above changed to call
acp_event_stream(client, until=prompt_task):The trade-off is in that last row and is worth stating plainly. Today an arbitrarily late
trailing event is still delivered, because the loop waits for it forever — which is the same
property that causes the hang. Bounding the wait means an event arriving more than one drain window
after
session/prompthas resolved is dropped.ACP_IDLE_POLL_SECONDSis set to 0.25s, the samebudget the adapters already chose for exactly this, so in practice this only affects events that
arrive after the turn has been reported complete. If you would rather not lose those at all, racing
events.get()againstprompt_taskwithasyncio.wait(..., return_when=FIRST_COMPLETED)wouldalso fix the hang without a polling window — it is a larger change, and I went with the smaller one.
The branch is here if it is useful: https://github.com/ciberick/computer/tree/fix/acp-stream-never-ends
I could not open a pull request —
POST /repos/open-webui/computer/pullsreturns 404 for an outsideaccount — so the change is offered here instead. Happy to submit it any way you prefer.
Notes
test. Glad to add one if you would like tests introduced.
cursor;cline,geminiandgrokshare the identical loop, so the same analysis applies to them, but Ihave not exercised them directly.
@owui-terminator[bot] commented on GitHub (Sep 24, 2026):
🔍 Related Issues Found
I found some existing issues that might be related. Please check if any of these are duplicates or contain helpful solutions:
🟢 #213 bug: a stalled agent session hangs the chat task forever, and the whole transcript is lost if you kill it
Related because it is another hang-at-end-of-turn problem in the agent pipeline. It describes the chat task waiting forever when no terminal event arrives, which matches the same failure mode of never reaching completion and only recovering when interrupted.
by ThalynLabs
🟢 #220 bug: Chat ends when Claude Code agent spawns a background agent and never resumes
Related at a higher level because it also involves agent sessions that do not resume/finish correctly after background activity. While the mechanism differs, it is in the same area of agent turn lifecycle management and post-tool completion.
by JKGeovision
🟢 #224 bug: Gemini streaming tool calls crash and lose thought signatures
Only loosely related through Gemini agent behavior, but it is a genuine ACP-adjacent Gemini bug that affects streaming/tool-call handling. It is not the same hang, so it is a lower-confidence relation.
by owahltinez
💡 If your issue is a duplicate, please close it and add any additional details to the existing issue instead.
This comment was generated automatically. React with 👍 if helpful, 👎 if not.
@ciberick commented on GitHub (Sep 24, 2026):
Thanks — I read all three. None is a duplicate, but #213 is closely adjacent and worth reading
alongside this one, so here is how I think they fit together.
#213 — same symptom, different cause; complementary rather than duplicate
#213 is about
opencode, which does not go through the ACP adapter (acp_event_streamis used onlyby
cursor.py,cline.py,gemini.pyandgrok.py). There the agent genuinely stalls — a toolpart stuck in
runningafter a failed spawn — so no terminal event is ever produced, and #213's askis structural: a watchdog, a bounded HTTP timeout, keeping stderr, and persisting incrementally.
This issue needs no stall at all. The agent finishes its turn correctly and emits everything it
should; the adapter simply never notices, because the completion check sits inside a loop body that
only runs when another event arrives. A healthy turn hangs.
The two meet in one place, which #213 already documents well:
chat_taskonly flushes the reply onAgentDoneor on cancellation, so whatever the cause, the transcript is stranded until the userinterrupts.
Worth noting in #213's favour: it argues the timeout gap is "structural rather than adapter-specific",
and this issue is evidence for that — the same end state is reachable with no misbehaving agent
anywhere.
They also do not substitute for each other:
only after the timeout, so a normal reply would arrive seconds or minutes late.
such as #213's own unresolved.
Happy to fold this into #213 if you would rather track it as one piece of work — it just seemed more
useful as a separate, small, self-contained fix.
#220 and #224 — unrelated as far as I can tell
hanging, on a different agent and code path.
and
stream_openai_completions(), a parsing crash. This issue'sgemini.pyis the separate ACPpath. Same provider name, different code; I think the match was on the word "Gemini".