Files
twenty/packages/twenty-front
Félix Malfait 4a8679327b fix(ai): bound silent stream recovery and surface a terminal CONNECTION_LOST state (#22486)
## Rationale

Two gaps in the keep-alive recovery (`AgentChatStreamKeepAliveEffect`):

1. It only engages when `isStreaming` is already true — a socket that
dies **before the first chunk** leaves the user waiting forever with no
recovery path (CONFIRMED-high in the chat-stack audit; the window where
Sentry shows failures concentrate).
2. When it does engage, it retries **silently forever** — a genuinely
dead connection means an infinite spinner with the user none the wiser.

## Why this is the root cause, not a symptom patch

Recovery must be gated on "a response is owed" — which since #22485 is
`isStreaming || isAwaitingFirstChunk`, closing gap 1 with the state that
actually models the window rather than a timer heuristic. For gap 2,
unbounded retry hides a terminal condition; the fix is an honest state
machine: 3 silent recoveries (resubscribe + refetch), then a client-only
`CONNECTION_LOST` error. Two deliberate choices from the audit:

- **No Retry button** on `CONNECTION_LOST` — it's semantically forced,
not cosmetic: Retry calls `retryLastFailedTurn`, which requires a
persisted `lastStreamError`; after a mere connection loss the server has
no failed turn (the stream is likely still running or completed
server-side), so Retry would deterministically throw
`NO_FAILED_TURN_TO_RETRY`.
- **Auto-clear instead of dead-end**: the moment events flow again (SSE
reconnect, refetch delivering data), the `CONNECTION_LOST` error clears
itself — the state is "connection lost", not "turn failed", and it
self-heals when the connection returns.

## User impact

A dead connection pre-first-token currently means waiting forever;
mid-stream it means silent infinite recovery. Now: three quiet recovery
attempts (which fix the transient cases invisibly), then a truthful
message, which disappears on its own when connectivity returns — and the
server-side answer is intact all along, delivered by the next successful
refetch.

## Stack

Based on #22485 (pending indicator) — reads the awaiting-first-chunk
state. Chain: #22484#22485 → this.

## Test plan

- [ ] CI green
- [ ] Manual: kill the network pre-first-token → 3 recoveries →
CONNECTION_LOST; restore network → error clears, transcript catches up

https://claude.ai/code/session_01Lyi6zTema2FMVVh8MD6c38

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Lyi6zTema2FMVVh8MD6c38)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22486?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-02 21:20:45 +02:00
..
2026-06-18 13:05:31 +00:00

Run yarn dev while server running on port 3000