Compare commits

..

1 Commits

Author SHA1 Message Date
Otto
8e8993be3c fix(copilot): prevent double output from StreamFinish/mark_task_completed race (#SECRT-2021)
Requested by @0ubbe

The CoPilot consistently displays every response twice. Root cause is a
race condition between StreamFinish delivery and mark_task_completed:

1. Executor publishes StreamFinish to Redis stream
2. Frontend receives it, transitions to 'ready', invalidates cache
3. mark_task_completed runs AFTER StreamFinish (separate operation)
4. Session refetch sees task still 'running' → spurious resumeStream()
5. Resume replays entire Redis stream from '0-0' → double output

Backend fix (processor.py): Intercept StreamFinish from the generator
instead of publishing it directly. mark_task_completed atomically sets
status to 'completed' THEN publishes StreamFinish, ensuring clients
never see the finish event while the task is still marked as running.

Frontend fix (useCopilotPage.ts): Only reset hasResumedRef on error
(SSE drop), not on clean stream finish. This prevents the spurious
resume even if the backend timing changes.
2026-02-23 13:58:45 +00:00
3 changed files with 19 additions and 14 deletions

View File

@@ -257,10 +257,21 @@ class CoPilotProcessor:
cluster_lock.refresh()
last_refresh = current_time
# Intercept StreamFinish: don't publish it directly.
# mark_task_completed atomically sets status to "completed"
# and THEN publishes StreamFinish. Publishing StreamFinish
# before the status update causes a race where the frontend
# sees the stream as finished but the task is still "running",
# triggering a spurious resume that replays the entire stream
# (double output bug — SECRT-2021).
if isinstance(chunk, StreamFinish):
break
# Publish chunk to stream registry
await stream_registry.publish_chunk(entry.task_id, chunk)
# Mark task as completed
# Mark task as completed — this publishes StreamFinish AFTER
# atomically setting status to "completed", preventing the race.
await stream_registry.mark_task_completed(entry.task_id, status="completed")
log.info("Task completed successfully")

View File

@@ -208,8 +208,13 @@ export function useCopilotPage() {
queryClient.invalidateQueries({
queryKey: getGetV2GetSessionQueryKey(sessionId),
});
// Allow re-resume if the backend task is still running.
hasResumedRef.current = null;
// Only allow re-resume on error (SSE drop without clean finish).
// On clean finish (status === "ready"), the backend task is done —
// resetting the ref would allow a spurious resume if the session
// refetch races with mark_task_completed (SECRT-2021).
if (status === "error") {
hasResumedRef.current = null;
}
}
}, [status, sessionId, queryClient]);

View File

@@ -218,17 +218,6 @@ If you initially installed Docker with Hyper-V, you **dont need to reinstall*
For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/).
### ⚠️ Podman Not Supported
AutoGPT requires **Docker** (Docker Desktop or Docker Engine). **Podman and podman-compose are not supported** and may cause path resolution issues, particularly on Windows.
If you see errors like:
```text
Error: the specified Containerfile or Dockerfile does not exist, ..\..\autogpt_platform\backend\Dockerfile
```
This indicates you're using Podman instead of Docker. Please install [Docker Desktop](https://docs.docker.com/desktop/) and use `docker compose` instead of `podman-compose`.
## Development