garrytan/gstack · error · Error

diagram-render bundle did not become ready in the browse tab

Error message

diagram-render bundle did not become ready in the browse tab (${READY_TIMEOUT_MS}ms). Check `browse js "window.__errors"` on tab ${this.tabId}.

What it means

Thrown by RenderTab.loadBundle() when the diagram-render bundle page does not expose a #status element reading 'ready' within READY_TIMEOUT_MS after loadHtmlFile. The bundle is the headless renderer for mermaid/excalidraw; if it never initializes, no fence can render. The message points at `window.__errors` on the tab for diagnostics.

Source

Thrown at make-pdf/src/diagram-prepass.ts:367

        if (!survivorOk) throw renameErr;
      }
    }
    const tabId = browseClient.newtab();
    const tab = new RenderTab(tabId, staged);
    tab.loadBundle();
    return tab;
  }

  /** (Re)load the bundle page — also the reset path after a render error. */
  loadBundle(): void {
    browseClient.loadHtmlFile({ file: this.stagedBundlePath, tabId: this.tabId });
    const ready = browseClient.waitForExpression({
      expression: "document.getElementById('status') !== null && document.getElementById('status').textContent === 'ready'",
      tabId: this.tabId,
      timeoutMs: READY_TIMEOUT_MS,
    });
    if (!ready) {
      throw new Error(
        "diagram-render bundle did not become ready in the browse tab " +
        `(${READY_TIMEOUT_MS}ms). Check \`browse js "window.__errors"\` on tab ${this.tabId}.`,
      );
    }
  }

  /**
   * Call one of the bundle's async window functions with JSON-safe string
   * args. Errors come back as a recognizable ERR: prefix so a render failure
   * is data, not a thrown browse exit.
   */
  call(fn: string, ...args: Array<string | number>): string {
    const argList = args.map((a) => JSON.stringify(a)).join(",");
    const expression =
      `window.${fn}(${argList})` +
      `.then(r => "OK:" + r)` +
      `.catch(e => "ERR:" + String((e && e.message) || e))`;
    const result = this.js(expression);

View on GitHub (pinned to 94993f7401)

Solutions

  1. Inspect bundle errors: `browse js "window.__errors"` on the tab id from the message.
  2. Rebuild the bundle: `bun run build:diagram-render` (repo) or re-run ./setup (install).
  3. Increase READY_TIMEOUT_MS if the daemon is cold and the machine is slow.
  4. Confirm the bundle HTML resolves to the right file (see resolveBundlePath error 329).

Example fix

// before
loadBundle(): void {
  browseClient.loadHtmlFile({ file: this.stagedBundlePath, tabId: this.tabId });
  const ready = browseClient.waitForExpression({ expression: "...status==='ready'", tabId: this.tabId, timeoutMs: READY_TIMEOUT_MS });
  if (!ready) throw new Error(`diagram-render bundle did not become ready ...`);
}

// after: capture boot errors into the thrown message
loadBundle(): void {
  browseClient.loadHtmlFile({ file: this.stagedBundlePath, tabId: this.tabId });
  const ready = browseClient.waitForExpression({ expression: "...status==='ready'", tabId: this.tabId, timeoutMs: READY_TIMEOUT_MS });
  if (!ready) {
    const errs = browseClient.js({ expression: 'JSON.stringify(window.__errors ?? [])', tabId: this.tabId });
    throw new Error(`diagram-render bundle did not become ready (${READY_TIMEOUT_MS}ms). Boot errors: ${errs}`);
  }
}
Defensive patterns

Strategy: retry

Try / catch

async function loadBundleWithRetry(tab: RenderTab, attempts = 2): Promise<void> {
  for (let i = 0; i < attempts; i++) {
    try {
      tab.loadBundle();
      return;
    } catch (e) {
      if (i === attempts - 1) throw e;
      // reload the tab from scratch before retrying
      await new Promise(r => setTimeout(r, 500));
    }
  }
}

Prevention

When it happens

Trigger: loadBundle() is called (initial load or the reset-after-error path) and waitForExpression times out. Causes: a bundle HTML file that is wrong/corrupt, a JS exception during bundle boot (visible in window.__errors), a wedged renderer, READY_TIMEOUT_MS too low for a cold Chromium, or a loadHtmlFile that silently failed.

Common situations: A stale diagram-render.html from an older build missing the status element; a CDN/runtime dependency the bundle expects is blocked (offline posture); Chromium sandbox failure on a fresh VM; the bundle was overwritten by a partial build; CJK or huge content exhausting memory at boot.

Understand the failure class

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/078eaea0909cd80a. Report an issue: GitHub.