Skip to content

bug: Workspace turn fails with "reached its usage limit" instead of walking every subscription — SUB-003 retries once, then hands the retry to the user #3470

Closed

Description

@vybe

Summary

A Workspace user sends a message, the agent's current subscription is at its usage limit, and the turn comes back as Failed with "The agent has reached its usage limit and can't respond right now. Please try again later." — even though the install has other subscriptions that could have served it.

Subscription exhaustion must be fully transparent to the Workspace user: when a turn hits a usage limit, the platform should switch the agent to another subscription and re-run the same turn itself, walking through every viable subscription (and the platform API-key fallback) until one answers. The user should only ever see a failure when the whole pool has been tried and nothing can serve.

Context

Today the remediation is bounded to one switch + one retry per turn, and in several paths it hands the retry back to the human:

  • services/task_execution_service.py — the post-failure SUB-003 hook is guarded by a one-shot subscription_switch_attempted flag: at most one handle_subscription_failure (or fallback_to_api_key) and one re-issue of the turn. If that second attempt also rate-limits — the alternative was itself near its wall — the turn lands FAILED although a third subscription may be healthy.
  • services/subscription_auto_switch.py — select_best_alternative_subscription honours a 2h kind-blind skip-list (has_recent_subscription_failures), so "try the others" can decline alternatives that would serve now. The bug(subscriptions): a Workspace message on a rate-limited subscription fails instead of switching and completing — close the SUB-003 gaps on the turn path #2638 readmission override only partially relaxes this.
  • services/chat_execution_service.py — the sync /chat path switches and then answers "Subscription auto-switched to '…'. Please retry." with retry_after: 15: the switch happened, but the user's message was not re-sent.
  • client_portal/service.py — the AUTH/BILLING branch either says "it was moved onto '…'. Send that again and it should go through." (auth_switched, retryable) or, when nothing was switched, emits the _usage_limit_detail 502 shown above (auth, not retryable). Both make the person do the platform's job; the second gives up on a pool that was never fully walked.

SUB-003 was designed around schedules, where one user-visible failure per switch was acceptable. In the Workspace an external client is sitting in front of a chat box: "try again later" against a pool with headroom is a product failure, not a degraded mode.

Acceptance Criteria

Technical Notes

Part 2 — the API-key fallback (#2638) can strand an agent on a key that cannot serve

Observed on a live install: an agent's subscription hit its limit mid-run, the switcher found no viable alternative, and fallback_to_api_key fired — it cleared the subscription assignment, turned on use_platform_api_key, and restarted the container (killing the run in flight). Every turn since then goes to the platform key and fails in seconds with "Credit balance is too low". A limit that resets in a few hours became a permanent outage.

Confirmed in services/subscription_auto_switch.py::fallback_to_api_key:

  • The only viability check is if not api_key — a configured key with no credit passes.
  • The assignment is cleared, not remembered (documented as deliberate: no return path), so nothing moves the agent back when the window resets.
  • Once on the API key, handle_subscription_failure's precondition (agent has a subscription) and the fallback's own if not old_sub_id: return None both no-op — the agent is stuck; a BILLING failure on the key has no remediation at all.
  • _restart_agent is unconditional, so a fallback triggered by one turn kills whatever other work the agent was doing.

Acceptance Criteria (Part 2)

  • The API-key fallback is a rung in the same retry loop, not a permanent reassignment: the turn is attempted on the key; if the key fails with an auth/billing-class error, that counts as one more exhausted candidate and the turn fails honestly with the pool's earliest reset time.
  • A key that is known-refused (recent BILLING/AUTH failure recorded against it) is not selected — the same "recently refused by the provider" exclusion that auto-switch picks an alternative subscription without checking headroom — it can move agents onto a nearly-exhausted one #2409 applies to subscriptions.
  • The agent's subscription assignment is preserved across the fallback (the fallback is per-turn routing, or the original assignment is recorded and automatically restored once the subscription's headroom sampler reports it can serve again). Operator reassignment remains a deliberate act, but the platform must not need one to recover from a transient limit.
  • The "Switched to the platform API key" notification stays, and gains a second notification when the agent returns to its subscription.
  • Regression tests: (a) subscription exhausted + key with no credit → one honest failure, assignment unchanged, agent not restarted into a dead state; (b) subscription exhausted + healthy key → turn served, assignment preserved, later turns return to the subscription once it can serve; (c) a key that failed BILLING is skipped on the next turn.

Activity

  1. added
    complexity-mediumComplexity: medium (board points 5-8)
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 9, 2026
  2. vybe commented on Oct 9, 2026

    @vybe
    ContributorAuthor

    Scope extended with Part 2: the #2638 API-key fallback can strand an agent on a platform key that cannot serve (no credit), clears the subscription assignment with no return path, and restarts the agent mid-run. Reproduced from a live install and confirmed against fallback_to_api_key. See the updated body.

  3. removed
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 9, 2026
  4. vybe commented on Oct 9, 2026

    @vybe
    ContributorAuthor

    /claim

  5. github-actions commented on Oct 9, 2026

    @github-actions

    Claimed by @vybe. Label status-in-progress added.

    Next steps:

    1. Create branch: feature/3470-<short-slug>
    2. Implement the feature
    3. Open a PR with Fixes #3470
  6. added
    status-in-devMerged to dev, awaiting release cut to main
    and removed on Oct 10, 2026
  7. removed
    status-in-devMerged to dev, awaiting release cut to main
    on Oct 26, 2026
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