Skip to content

BUG: tps-meter.tsx pervasive silent catch-all error handling obscures real runtime bugs #18

Description

@devinoldenburg

Summary

plugins/tps-meter.tsx wraps virtually every event handler, reactive computation, and side effect in silent catch {} blocks. While the stated intent is "defensive — fail closed," the implementation catches all error types including TypeError, ReferenceError, and programming bugs. This makes runtime debugging of the plugin nearly impossible because legitimate failures are silently swallowed with zero visibility — no console, no fallback UI, nothing.

Affected Code

File: plugins/tps-meter.tsx

Location Lines What's caught silently
onPart handler 117-119 Malformed events, but also property access errors, type errors
onMessage handler 135-137, 145-146 Parts API failures, but also null deref, type errors
onMessageRemoved 160-162 Any error in map delete / property access
onPartRemoved 169-171 Any error in map delete
Event bus subscription loop 185-186 Event type not supported
Top-level event bus access 190-192 No event bus at all
Interval tick 197-199 RateMeter.sample failures
Cleanup off handlers 206-208 Cleanup call failures
View compute (createMemo) 218-222 State.session.messages lookup failures
Session status lookup 237-240 Status API failures
tui() registration 314-316 Any registration failure

That's 11 catch blocks across a single 319-line file, and 10 of them are empty catch {} or catch { /* ignore */ }.

Why This Is a Problem

The architectural principle ("fail closed, render nothing") is sound. But the implementation conflates two very different failure modes:

  1. Expected / recoverable: The OpenCode runtime is a different version and the API shape changed (API drift). The plugin should indeed render nothing.
  2. Unexpected / bugs: A misspelled variable name, a logic error in the handler body, or a SolidJS reactivity violation. These should be visible to the developer during development and testing.

By catching all errors identically, bug #2 is indistinguishable from scenario #1. A developer testing a change to the onMessage handler would see "nothing renders" with no hint that they have a TypeError on a specific line.

Concrete Example

If a developer accidentally writes:

const onMessage = (event) => {
  try {
    const info = event?.properties?.info;
    if (info && info.sessionID === props.sessionID && isAssistant(info) && info.time?.completed) {
      const generated = (Number(info.tokens?.ouput) || 0) + ...  // BUG: "ouput" not "output"

The catch {} on line 145 silently swallows the resulting NaN propagation / undefined behavior. The developer sees "the TPS section disappeared" with zero diagnostic information.

Recommended Fix

Use a guarded error boundary pattern: catch only the specific errors that indicate API drift (missing properties, wrong method signatures), and let unexpected errors propagate during development while still failing closed in production.

At minimum, log unexpected errors to console.warn or console.debug when process.env.NODE_ENV !== "production":

catch (err) {
  if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
    console.warn("[opencode-tps-meter] handler error:", err);
  }
}

Even better, check the specific error type:

catch (err) {
  // API drift: an expected property or method is missing → fail closed.
  if (err instanceof TypeError && /is not a function|undefined|read properties/.test(err.message)) {
    return; // API shape changed — expected, render nothing.
  }
  // Unexpected error: log it so developers can see the real problem.
  console.warn("[opencode-tps-meter] unexpected error:", err);
}

Related

  • The verify-plugin tool (tools/verify-plugin.mjs) tries to detect some errors by checking for "renderer" in the error message during component construction — but this only catches one specific error class, and only when Bun is available with peer deps installed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions