Skip to content

fix: surface usage-limit errors and fix model change broadcast#126

Merged
teng-lin merged 8 commits intomainfrom
investigate/usage-limit-error
Feb 22, 2026
Merged

fix: surface usage-limit errors and fix model change broadcast#126
teng-lin merged 8 commits intomainfrom
investigate/usage-limit-error

Conversation

@teng-lin
Copy link
Copy Markdown
Owner

@teng-lin teng-lin commented Feb 22, 2026

Summary

  • Surface OpenCode usage-limit errors: OpenCode's session.status { type: "retry" } was silently dropped — T3 put retry info in metadata but omitted the status field, so T4 broadcast status: null and the frontend ignored it entirely. Fix adds status: "retry" to the T3 output and threads it end-to-end: new retryInfo store field, ws.ts handler, and a red error banner in StreamingIndicator
  • Fix model change not reflected in UI: When a consumer sends set_model, the Claude CLI accepts it (confirmed via control_response { success }) but the bridge never updated session.state.model or notified consumers. Fix optimistically updates session state and broadcasts session_update { model } immediately — guarded so it's a no-op when no backend session exists
  • Docs: Added pnpm start trace command examples, status_change retry documentation, and a rebuild/restart guide to DEVELOPMENT.md

Test plan

  • pnpm test — all 2898 tests pass
  • Coverage on all changed files ≥ 80% across statements, branches, functions, lines (verified: lowest is 85.5% branch on unified-message-router.ts)
  • OpenCode session hitting usage limit: red error banner with message appears in UI
  • Selecting a different model in the model picker: model name updates in the session header immediately
  • Model picker with no active backend session: no crash, no stale broadcast

🤖 Generated with Claude Code

OpenCode's rate-limit retries were silently dropped: the status_change
message carried retry info only in metadata (no status field), so T4
broadcast status: null and the frontend ignored the metadata entirely.

- T3: add status: "retry" to the retry case metadata so handleStatusChange
  picks it up and broadcasts status: "retry" with full retry context
- Protocol: add "retry" to the status_change status union in both
  consumer-messages.ts and shared/consumer-types.ts
- Store: add retryInfo field + setRetryInfo action; extend sessionStatus
  type to include "retry"
- ws.ts: set retryInfo on retry status, clear it on any other status
- StreamingIndicator: render message + attempt count with warning dot
  when status === "retry"; no stop button shown during retry wait
Usage limits are plan quota exhaustion — not transient rate limits.
Retrying won't resolve them. Show error styling immediately and drop
the attempt counter, which implied the issue would self-resolve.
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @teng-lin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the user experience by surfacing OpenCode usage limit errors to the frontend. Previously, these critical errors were not communicated to the user, leading to a silent failure state. The changes involve updating the communication protocol, enhancing frontend state management to store retry-specific information, and modifying the UI to clearly display these errors, guiding users on necessary actions.

Highlights

  • Root Cause Addressed: Fixed an issue where OpenCode usage limit errors (type: "retry") were silently dropped, preventing the frontend from displaying them to the user.
  • Protocol Extension: Extended the status_change protocol to include a "retry" status, allowing the backend to explicitly signal rate-limiting or usage limit exhaustion.
  • Frontend State Management: Introduced retryInfo into the frontend session store to hold details about the retry state (message, attempt, next retry time).
  • Enhanced User Experience: Modified the StreamingIndicator component to display usage limit errors prominently in red, without an attempt counter or stop button, clearly indicating that user action is required.
  • Developer Documentation: Updated DEVELOPMENT.md with new pnpm start commands, a rebuild/restart guide, and documentation for the status_change retry values.
Changelog
  • DEVELOPMENT.md
    • Updated pnpm start commands for local development.
    • Added instructions for tracing with full payload output.
    • Included a new "Rebuild and Restart Guide" for frontend and backend changes.
    • Documented the new "retry" value for status_change messages.
  • shared/consumer-types.ts
    • Extended the status type in ConsumerMessage for status_change to include "retry".
  • src/adapters/opencode/opencode-message-translator.test.ts
    • Added an assertion to verify that status: "retry" is included in the metadata for retry events.
  • src/adapters/opencode/opencode-message-translator.ts
    • Modified translateSessionStatus to explicitly set status: "retry" in the metadata for retry events.
  • src/core/messaging/unified-message-router.test.ts
    • Added a new test case to ensure status: "retry" messages and their metadata are correctly broadcasted.
  • src/types/consumer-messages.ts
    • Extended the status type in ConsumerMessage for status_change to include "retry".
  • web/src/components/StreamingIndicator.test.tsx
    • Added tests for the retry state, verifying the display of the retry message, the absence of an attempt counter, and the hidden stop button.
  • web/src/components/StreamingIndicator.tsx
    • Implemented conditional rendering to display retry information with error styling when sessionStatus is "retry".
    • Accessed retryInfo from the store to display the specific error message.
  • web/src/store.ts
    • Added retryInfo field to SessionData to store retry-specific details.
    • Updated setSessionStatus to accept "retry" as a valid status.
    • Introduced setRetryInfo action to manage the retryInfo state.
    • Initialized retryInfo to null in emptySessionData.
  • web/src/ws.test.ts
    • Added tests to confirm retryInfo is correctly stored when a status_change message with status: "retry" is received.
    • Added a test to ensure retryInfo is cleared when the status changes from "retry" to a non-retry status.
  • web/src/ws.ts
    • Modified handleMessage to process status_change messages, setting sessionStatus and populating retryInfo if the status is "retry".
    • Ensured retryInfo is cleared for any non-"retry" status changes.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully surfaces OpenCode usage limit errors to the frontend by adding a new retry status. The changes span the entire stack, from the backend message translator and protocol types to the frontend store, WebSocket handler, and UI components, and include corresponding tests and documentation updates. The implementation is solid, but I've identified one area for improvement in the frontend WebSocket handler to enhance type safety when processing incoming message metadata, preventing potential silent UI failures.

Comment thread web/src/ws.ts
Comment on lines +313 to +321
if (msg.status === "retry" && msg.metadata) {
store.setRetryInfo(sessionId, {
message: msg.metadata.message as string,
attempt: msg.metadata.attempt as number,
next: msg.metadata.next as number,
});
} else {
store.setRetryInfo(sessionId, null);
}
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.

medium

Using type assertions (as string, as number) on values extracted from msg.metadata is unsafe because these values are of type unknown. As per repository guidelines, it's crucial to perform runtime typeof checks on unknown values before using them to ensure type safety and handle potential malformed data gracefully. If the backend sends a retry status with malformed or missing metadata, this will store undefined values in the store, leading to silent UI failures (e.g., an error indicator without a message).

Suggested change
if (msg.status === "retry" && msg.metadata) {
store.setRetryInfo(sessionId, {
message: msg.metadata.message as string,
attempt: msg.metadata.attempt as number,
next: msg.metadata.next as number,
});
} else {
store.setRetryInfo(sessionId, null);
}
if (msg.status === "retry" && msg.metadata) {
const { message, attempt, next } = msg.metadata;
if (
typeof message === "string" &&
typeof attempt === "number" &&
typeof next === "number"
) {
store.setRetryInfo(sessionId, { message, attempt, next });
} else {
store.setRetryInfo(sessionId, null);
}
} else {
store.setRetryInfo(sessionId, null);
}
References
  1. Before using or casting a value of type unknown, perform a runtime typeof check to ensure its type and prevent unsafe operations.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 459ffbc — replaced the as casts with runtime typeof checks matching the suggestion exactly. Also added a test that sends malformed retry metadata (wrong types) and asserts retryInfo stays null.

When a consumer sends set_model, the bridge sends a control_request to
the Claude CLI which responds with control_response { success }. However,
that response was silently dropped (only initialize responses are handled),
so session.state.model was never updated and no session_update was
broadcast to consumers — leaving the frontend model picker stale.

Fix: optimistically update session.state.model and broadcast
session_update { model } immediately when sendSetModel is called.
@teng-lin teng-lin changed the title fix: surface opencode usage limit error to frontend fix: surface usage-limit errors and fix model change broadcast Feb 22, 2026
@teng-lin teng-lin merged commit 4f882ad into main Feb 22, 2026
6 checks passed
@teng-lin teng-lin deleted the investigate/usage-limit-error branch February 22, 2026 22:16
@teng-lin teng-lin restored the investigate/usage-limit-error branch February 23, 2026 01:16
@teng-lin teng-lin deleted the investigate/usage-limit-error branch February 25, 2026 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant