Skip to content

fix(session): reconcile severed long turns against server state — no false 'failed to send' (#1376)#1377

Merged
vybe merged 6 commits into
devfrom
AndriiPasternak31/issue-1376
Jun 30, 2026
Merged

fix(session): reconcile severed long turns against server state — no false 'failed to send' (#1376)#1377
vybe merged 6 commits into
devfrom
AndriiPasternak31/issue-1376

Conversation

@AndriiPasternak31

Copy link
Copy Markdown
Contributor

Summary

Fixes the Session-mode false-failure (#1376): a long-running turn shows "Failed to send message" while the backend execution is still running and ultimately succeeds. The Session turn endpoint is synchronous, so an intermediary with a read timeout shorter than the axios ceiling (notably an external Cloudflare ~100s edge timeout) can sever the socket mid-turn — axios sees a network error and the catch rendered an unconditional failure.

This is the stay-on-page / proxy-timeout variant of the previously-fixed #759 (return-navigation variant). Frontend-only fix; no backend or schema change.

What changed

  • Reconcile before failing (stores/sessions.js): on any send error, sendMessage now reconciles against GET /sessions/{id} before declaring failure.
    • A new assistant beyond the pre-send baseline → success (checked first, because the assistant row persists inside the inflight sentinel — handing off there would let the poller rebaseline past the landed reply and false-fail).
    • turn_in_progress=true → hand off to the reattach poller (spinner stays, no error).
    • Neither → surface the real error; a pre-persistence drop restores the optimistic user row so the typed message stays visible.
    • A 429 concurrent-turn reject short-circuits before the reconcile (the sentinel reflects the OTHER turn).
  • Wall-clock poll deadline (startPolling): replaces the MAX_ATTEMPTS=60 (~22 min) attempt count — unreliable under background-tab timer throttling — with a ~2h deadline covering the sentinel TTL. The deadline outcome routes through a neutral notice (noticeBySession), not a red error.
  • UX (SessionPanel.vue): neutral status box (role="status"), aria-live on both error and notice so async outcomes are announced, and a "Still working…" spinner copy after ~75s kept alive across the poller handoff.
  • E2E (e2e/session-tab.spec.js): T1–T8 stub the POST (sever / 502 / 429) and GET with server-truth fixtures — no real Claude turn.
  • Docs: session-tab feature flow + Recent Updates index row.
  • Security: /cso --diff daily report — clean, no findings.

Acceptance criteria

  • A turn outliving an intermediary timeout never shows a false "failed to send" while running or succeeded.
  • On a client-side error, sendMessage checks turn_in_progress / latest assistant and reattaches instead of unconditionally setting errorBySession.
  • The poll path is bounded by a wall-clock deadline (≥ sentinel TTL), not an attempt count below the execution timeout.
  • A genuine backend failure still surfaces a real error (no regression).
  • Reverse-proxy read timeout: identified as an external Cloudflare edge timeout (operator config, outside this repo); the turn is now made resilient to the dropped socket rather than requiring a proxy change.

Closes #1376

🤖 Generated with Claude Code

AndriiPasternak31 and others added 5 commits June 29, 2026 23:33
…ing (#1376)

In Session mode the turn endpoint is synchronous; an intermediary (e.g.
Cloudflare's ~100s edge timeout) can sever the socket while the backend
keeps running and ultimately succeeds. The catch in sendMessage now
reconciles against GET /sessions/{id} before declaring failure: a NEW
assistant beyond the pre-send baseline is success (checked first, since
the reply persists inside the inflight sentinel); turn_in_progress hands
off to the reattach poller with no error; neither surfaces the real
error. A 429 concurrent-turn reject short-circuits before the reconcile.
A pre-persistence drop restores the optimistic user row.

startPolling now bounds on a wall-clock deadline (~2h, covering the
sentinel TTL) instead of an attempt count that background-tab throttling
made unreliable, and routes the deadline outcome through a neutral
noticeBySession channel instead of errorBySession.

Refs #1376

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…1376)

Surface the new noticeBySession channel as a non-danger status box
(role="status"/aria-live="polite") so a deadline-reached poll reads as a
status, not a red error. Add role="alert"/aria-live="assertive" to the
error box so an async outcome landing minutes after the click is
announced to screen readers. After ~75s flip the spinner copy to "Still
working… this may take a while.", kept alive across the poller handoff so
it doesn't snap back to "Thinking…" while the turn is still in flight.

Refs #1376

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "long-turn resilience" describe block that stubs the turn POST
(sever / 502 / 429) and GET /sessions/{id} with server-truth fixtures
via page.route — no real Claude turn. Covers: severed-but-running
(spinner, no error), severed-but-succeeded (reply renders), genuine 502
(real error), severed-then-failed ("No reply came back"), pre-persistence
false-success guard, deadline -> neutral notice (via the
__TRINITY_SESSION_POLL_DEADLINE_MS__ test seam), T7 assistant-present-
AND-sentinel-still-set -> success not false "no reply", and T8 a 429
concurrent reject -> real error, never a silent reattach.

Refs #1376

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the severed-socket reconcile row to the session-tab failure-mode
table, update the e2e and known-limitations sections to reflect the
mostly-fixed long-turn behavior, and add the Recent Updates index row.

Refs #1376

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Daily-mode /cso --diff over the #1376 branch working tree: zero net new
attack surface (frontend-only, reuses per-user-scoped session endpoints),
no XSS sink (Vue text interpolation, no v-html), no IDOR. Result: clean.

Refs #1376

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AndriiPasternak31 AndriiPasternak31 added type-bug Bug fix priority-p1 Critical path theme-reliability Theme: Reliability labels Jun 29, 2026
@AndriiPasternak31
AndriiPasternak31 marked this pull request as ready for review June 29, 2026 22:48

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated via /validate-pr: Closes #1376, e2e T1–T8 regression cases name the issue, session-tab flow doc updated, CSO --diff clean, security scan clean. Reconciles severed long turns against server state — no false 'failed to send'. LGTM.

# Conflicts:
#	docs/memory/feature-flows.md
@vybe
vybe merged commit 88e9929 into dev Jun 30, 2026
19 checks passed
pull Bot pushed a commit to bryanwills/trinity that referenced this pull request Jul 9, 2026
…ost-release features

Sync user docs with everything user-facing since the 2026-06-22 sync
(123 commits, including the v0.7.0 release). All content code-derived.

New pages:
- whats-new/v0.7.0.md — user-facing release highlights (PostgreSQL for
  production + SQLite EOL 2026-09-01, Codex runtime, portable agent data,
  credential hot-reload, agent-isolation hardening)
- advanced/voice-replies.md — canonical outbound-TTS page (epic Abilityai#24:
  Abilityai#25 Telegram, Abilityai#26 Slack, trinity-enterprise#56 WhatsApp)

Major updates:
- agent-sharing.md rewritten for the Access-tab/Sharing-tab split
  (trinity-enterprise#17/Abilityai#18/Abilityai#19/Abilityai#20): operator roster, Restricted↔Open,
  client roster, channel dialogs, public chat model (Abilityai#894), public-only
  custom instructions (Abilityai#1205)
- setup.md: setup-token flow removed (trinity-enterprise#49), required
  admin email + email sign-in (Abilityai#82), opt-in intake (trinity-enterprise#38)
- agent-chat.md / agent-session.md: unified Chat tab + Session-mode
  toggle (Abilityai#1112); severed-turn reconcile (Abilityai#1377)
- deploy spokes: DATABASE_URL/Postgres path + SQLite EOL (Abilityai#300/Abilityai#1183/Abilityai#1278),
  pg_dump backup notes; fixes single-server.md's stale "not yet selectable"
- whatsapp-integration.md: webhook must route to backend (Abilityai#1281),
  outbound media (Abilityai#1315)
- mcp-server.md: tool catalog corrected to 93 tools; operator-queue
  respond (Abilityai#1104); dedicated chat_with_<slug> exposure (Abilityai#846)
- smaller: Slack identity/icon/proactive (Abilityai#350, Abilityai#292), VoIP panel +
  persisted voice + Gacrux (trinity-enterprise#28), curated credential
  paths (Abilityai#1305/#11), key rotation (Abilityai#267), hot-reload rotation (Abilityai#1089),
  parallel-tasks ceiling (Abilityai#506), monitoring UI toggle (Abilityai#1409), JWT logout
  revocation (Abilityai#187), cancelled status (Abilityai#679), schedule scorecards (Abilityai#1115),
  Codex runtime (Abilityai#1187)

Verification: whats-new codename/issue-number grep clean; enterprise-docs
guard pattern clean; deploy-guide public-safety greps clean; all relative
links resolve (4 pre-existing broken links fixed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority-p1 Critical path theme-reliability Theme: Reliability type-bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants