[BUG] Logs tab renders blank: UI does JSON.parse(f.text) but records carry the payload nested under the record type

Author: giovannircoCreated Sep 4, 2026Updated Sep 4, 2026

Describe the bug

The Logs tab in the session UI renders a blank page and unmounts. The API and the bundled UI disagree on the shape of a log record.

The UI opens a WebSocket to wss://browser.example.com/v1/sessions/logs and, for each record, does:

javascript
r.slice(-40).map(f => { const p = JSON.parse(f.text), m = u(f.type, p); ... })

But records pushed over that socket have no top-level text field. The payload is nested under a key named after the record type:

json
{"type":"Console","timestamp":"...","pageId":"...","targetType":"page","console":{"level":"log","text":"hello","loc":":49:49"}}
{"type":"Request","timestamp":"...","pageId":"...","targetType":"page","request":{"method":"GET","url":"https://example.com/","resourceType":"Document"}}
{"type":"Response","timestamp":"...","pageId":"...","targetType":"page","response":{"status":200,"url":"https://example.com/","mimeType":"text/html"}}
{"type":"Navigation","timestamp":"...","pageId":"...","targetType":"page","navigation":{"url":"https://example.com/"}}
{"type":"BrowserInteraction","timestamp":"...","pageId":"...","targetType":"page","interaction":{"action":"navigate","eventType":"framenavigated",...}}

So f.text is undefined, JSON.parse(undefined) coerces to the string "undefined", and it throws:

SyntaxError: "undefined" is not valid JSON
    at JSON.parse (<anonymous>)
    at .../ui/assets/index-<hash>.js
    at Array.map (<anonymous>)

The throw happens inside map during render, so React unmounts the tab and the panel goes blank — no partial output, no error surfaced to the user.

I captured 10 consecutive records off the socket spanning Console / Request / Response / Navigation / BrowserInteraction; every one had typeof f.text === "undefined".

Steps to reproduce

  1. Run a self-hosted Steel instance and create a session.
  2. Open the session in the UI and select the Logs tab.
  3. Cause any browser activity in that session (a navigation, or a console.log).
  4. The tab goes blank. The browser console shows the SyntaxError above.

The socket itself is healthy — connecting to wss://browser.example.com/v1/sessions/logs directly shows records arriving normally, so this is purely a consumer-side shape mismatch.

Root cause and suggested fix

The formatter immediately next to the failing line already expects exactly the nested payload, not a parsed text blob:

javascript
u = (f, p) =>
    f === "Console"    ? (p.message ? p.message.replace(/^\d{2}:\d{2}:\d{2}\.\d{3}\s+(INFO|WARN|ERROR|DEBUG)\s+/, "")... : p.text)
  : f === "Request"    ? `[${p.method}] ${p.url}`
  : f === "Response"   ? `[${p.status}] ${p.url}`
  : f === "Error"      ? p.message
  : f === "Navigation" ? (p.url || JSON.stringify(p))
  :                      (p.message || JSON.stringify(p));

p.method / p.url line up with f.request, p.status / p.url with f.response, p.text with f.console. So passing the nested object straight through makes the tab render correctly, not merely stop crashing:

javascript
const p = f.console ?? f.request ?? f.response ?? f.navigation ?? f.interaction ?? f;

(A generic f[f.type.toLowerCase()] does not work, because BrowserInteraction carries its payload under interaction.)

It looks like JSON.parse(f.text) is left over from an older record schema where the payload was a JSON string on text; the emitter has since moved to nested objects and the consumer was not updated.

Verified

I applied exactly that substitution to the served bundle on a self-hosted instance. The tab now renders, with no page errors:

09:25:24 [Console] CONSOLE-PROBE-12345
09:25:24 [Console] a-warning-here
09:24:48 [Request]  [GET] https://example.com/
09:24:48 [Response] [200] https://example.com/
09:24:48 [Navigation] https://example.com/

Environment

  • Image: ghcr.io/steel-dev/steel-browser@sha256:6dd29f73c33bb0f5bc9d950e8e4492c3f8458a06313346fa1767f68936a36fc8 (the current latest, as of 2026-09-04)
  • Chrome 151.0.7922.173
  • Self-hosted behind an HTTPS reverse proxy; USE_SSL=true
  • Reproduces with LOG_STORAGE_ENABLED both set and unset — it is not related to log persistence