Skip to content

agent-browser: large (>16KB) tabs snapshots misclassified as frames and dropped #296

Description

@dormouse-bot

Large (>16KB) tabs snapshots are misclassified as frames and dropped

Surfaced by the nightly code-quality survey.

In agent-browser-connection.ts, handleMessage routes stream messages purely by size: any string longer than FRAME_PULSE_THRESHOLD (16384) is treated as a frame. The comment at L8-L10 states the assumption directly: "Stream messages above this size are frames (a base64 JPEG); status/tabs are small JSON control messages."

But a tabs message carries url + title per tab, so a user with many open tabs (long URLs/titles) can push a tabs snapshot past 16KB. When that happens the tab update is silently lost:

  • If wantFrameData() is false (the idle hot path), the payload is never parsed — L273-L274 hash the raw envelope and emit a bare frame-pulse. The tabs never reach handleTabs.
  • If wantFrameData() is true, it parses, sees msg.type === 'tabs' (not 'frame'), falls out of the inner if without returning, and hits the same L273-L274 fallback — again dropping the tabs.

Because the daemon re-broadcasts tabs on its heartbeat, a user whose tab list consistently exceeds the threshold would see the tab list / active-tab selection never update until an unrelated smaller snapshot happens to arrive.

Why this is filed rather than fixed directly

The clean fix depends on the daemon's wire contract, which I can't determine from the client alone:

  • Can control messages (tabs/status) legitimately exceed 16384 bytes? If the daemon guarantees they can't, the current size-as-discriminator is fine and this is a non-issue.
  • Is the frame envelope's shape stable enough for a cheap structural discriminator (e.g. peeking for a "type":"frame" prefix) so large control messages can be routed to normal dispatch without paying a full JSON.parse of ~100KB frames at ~20Hz?
  • Should the threshold instead gate only actual frames, with control messages always dispatched regardless of size?

Each of these is a protocol decision. The performance intent of the threshold (avoid parsing large frame JSON on the idle path — see L256-L260) must be preserved, so the fix shouldn't just "parse everything."

Suggested directions

  1. When wantFrameData() is true and the parsed large message is a known control type (tabs/status), dispatch it through the normal path instead of dropping it — fixes the hover case with no hot-path cost.
  2. For the idle case, add a cheap frame discriminator (structural prefix check) so a large non-frame message is routed to control handling without a full parse.
  3. Whatever is chosen, add a regression test for a tabs payload that crosses FRAME_PULSE_THRESHOLD.

Severity: low-to-medium — real correctness bug, but gated on a large tab count.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions