fix: surface usage-limit errors and fix model change broadcast#126
fix: surface usage-limit errors and fix model change broadcast#126
Conversation
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.
Summary of ChangesHello @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
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| 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); | ||
| } |
There was a problem hiding this comment.
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).
| 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
- Before using or casting a value of type
unknown, perform a runtimetypeofcheck to ensure its type and prevent unsafe operations.
There was a problem hiding this comment.
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.
Summary
session.status { type: "retry" }was silently dropped — T3 put retry info inmetadatabut omitted thestatusfield, so T4 broadcaststatus: nulland the frontend ignored it entirely. Fix addsstatus: "retry"to the T3 output and threads it end-to-end: newretryInfostore field,ws.tshandler, and a red error banner inStreamingIndicatorset_model, the Claude CLI accepts it (confirmed viacontrol_response { success }) but the bridge never updatedsession.state.modelor notified consumers. Fix optimistically updates session state and broadcastssession_update { model }immediately — guarded so it's a no-op when no backend session existspnpm starttrace command examples,status_changeretry documentation, and a rebuild/restart guide toDEVELOPMENT.mdTest plan
pnpm test— all 2898 tests passunified-message-router.ts)🤖 Generated with Claude Code