Skip to content

bug(workspace): failed turns are invisible to the client — fast failures misreported as "lost track", backend error discarded, Retry suppressed where it is safe #2320

Description

@trinity-ability

What happened (observed on a live instance, evidence in the execution row)

A Workspace message created an execution that failed 4.6 seconds later with HTTP 429 — the agent's Claude subscription hit its weekly limit, and the SUB-003 auto-switcher correctly reported no viable alternative (the whole pool was exhausted). The backend composed a precise, actionable error (limit + reset time + remediation), wrote it to schedule_executions.error, logged it, and ended the turn cleanly.

The Workspace client showed:

Still no reply — we've lost track of this turn. It may still finish; check the conversation shortly.

Every clause of that is wrong for this case: the turn did finish (failed), it will never produce a reply, and the composed error was discarded. Retrying "as-is" would 429 again, but the user has no way to know that.

Mechanism

The client learns a turn's outcome exactly two ways (PortalConversation.vue::awaitPersistedReply):

  1. a new assistant message in persisted portal history, or
  2. the in-flight marker (in_flight_execution_id) still being set.

A failed turn produces neither: no assistant/error message is persisted to enterprise_portal_messages, and start_portal_turn._run's finally clears the marker on every exit path (client_portal/service.py:1843-1846). The failure record lives only in schedule_executions.error (_fail_unstarted_execution, service.py:1702), which no portal read surface exposes — the history response carries in_flight_execution_id / in_flight_wait_budget_seconds but no last-turn outcome.

So after the server reports "nothing running" for REPLY_IDLE_GIVE_UP_MS (6s), the client returns { lost: true, idle: true } (PortalConversation.vue:899) — and deliver() (line 754) renders the #2133 "lost track" message designed for turns that ran and got lost. The idle: true flag that distinguishes "server says nothing is running" from "wait budget exhausted" is returned but never read — even the distinction the client already has goes unrendered.

Net effect, on every fast-failing Workspace turn (subscription/auth 429·503, agent stopped mid-dispatch, resume-lock contention, any pre-start ClientPortalError):

Constraints on the fix

  • schedule_executions.error must NOT be exposed verbatim to portal clients — it is free-form runtime text on a client-facing surface (the fix(workspace): Reports tab shows raw JSON to clients — reuse the display_hint renderers #2162/G-04 discipline). Persist/expose a typed, sanitized failure: a category (capacity, auth, agent_unavailable, busy, internal) plus a client-safe sentence; the full detail stays on the operator surfaces (Executions) where it already lands. Subscription-limit remediation ("add an API key / register a subscription") is operator guidance, not client guidance.
  • Retry may only be offered when the failure is provably pre-start/unbilled (the typed record can carry that bit). The lost/idle paths keep no-Retry.

Suggested shape

  1. On the _run failure paths, persist a failure record the client can see — either a typed error message row in portal history, or a last_turn_outcome: {execution_id, status, category, message} field on the history response (the client already polls it).
  2. Client: render "This turn failed: " instead of the lost-track message when an outcome is present; offer Retry iff the record says pre-start/unbilled.
  3. Minimal interim step (frontend-only): read the existing idle flag and word the two give-ups differently — "The agent did not reply" (server says nothing running) vs today's "lost track" (budget exhausted while the marker lived).

Acceptance criteria

Related: #2120, #2133, #2214, #2319 (the slow twin: post-lost recovery for turns that really ran), abilityai/trinity-enterprise#286, abilityai/trinity-enterprise#358

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions