garrytan/gstack · error · BrowseClientError

1

1

Error message

browse newtab exited 1: could not parse tab id from: ${out}

What it means

BrowseClientError(1, 'newtab', ...) thrown when neither the --json path nor the legacy stdout regex `/tab\s+(\d+)/i` could extract a tab id from `browse newtab` output. The fallback exists for older browse builds that lack --json; if both fail the client cannot proceed because subsequent calls need a numeric tabId.

Source

Thrown at make-pdf/src/browseClient.ts:239

 * Open a new tab. Returns the tabId.
 * Requires `$B newtab --json` to be available (added in the browse flag
 * extension for this feature). If --json isn't supported yet, the fallback
 * parses "Opened tab N" from stdout.
 */
export function newtab(url?: string): number {
  const args = ["newtab"];
  if (url) args.push(url);
  // Try --json first (preferred path for programmatic use)
  try {
    const out = runBrowse([...args, "--json"]);
    const parsed = JSON.parse(out);
    if (typeof parsed.tabId === "number") return parsed.tabId;
  } catch {
    // Fall back to stdout-string parsing. Brittle, but works on older browse builds.
  }
  const out = runBrowse(args);
  const m = out.match(/tab\s+(\d+)/i);
  if (!m) throw new BrowseClientError(1, "newtab", `could not parse tab id from: ${out}`);
  return parseInt(m[1], 10);
}

/**
 * Close a tab (by id or the active tab).
 */
export function closetab(tabId?: number): void {
  const args = ["closetab"];
  if (tabId !== undefined) args.push(String(tabId));
  runBrowse(args);
}

/**
 * Load raw HTML into a specific tab.
 * Uses --from-file for any payload >4KB (Codex round 2 #3).
 */
export function loadHtml(opts: LoadHtmlOptions): void {
  // Always use --from-file to dodge argv limits. The HTML is almost always >4KB.

View on GitHub (pinned to 94993f7401)

Solutions

  1. Upgrade browse to a build that supports `newtab --json` (the preferred path).
  2. Run `browse newtab --json` manually and inspect the output to confirm tabId is present.
  3. If --json works but returns a different key, update the parser in newtab().
  4. Restart the browse daemon to clear the bad-state stdout.

Example fix

// before
const out = runBrowse(args);
const m = out.match(/tab\s+(\d+)/i);
if (!m) throw new BrowseClientError(1, 'newtab', `could not parse tab id from: ${out}`);
return parseInt(m[1], 10);

// after: prefer --json strictly, broaden fallback regex, log raw output on failure
try {
  const parsed = JSON.parse(runBrowse([...args, '--json']));
  if (typeof parsed.tabId === 'number') return parsed.tabId;
  if (typeof parsed.id === 'number') return parsed.id; // alternate key
} catch { /* legacy */ }
const out = runBrowse(args);
const m = out.match(/tab\s*(?:id)?[:\s]*(\d+)/i);
if (!m) throw new BrowseClientError(1, 'newtab', `could not parse tab id from: ${JSON.stringify(out)}`);
return parseInt(m[1], 10);
Defensive patterns

Strategy: validation

Validate before calling

// Probe browse capabilities once and pick the newtab strategy.
function supportsNewtabJson(): boolean {
  try {
    const out = runBrowse(['newtab', '--help']);
    return /--json/.test(out);
  } catch {
    return false;
  }
}

const useJson = supportsNewtabJson();

Try / catch

try {
  return newtab(url);
} catch (e) {
  if (e instanceof BrowseClientError && e.command === 'newtab' && /could not parse/i.test(e.message)) {
    // upgrade prompt: this browse build is too old
    throw new Error('browse build too old for newtab — re-run ./setup');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling newtab(url) where browse is too old to support --json AND its stdout text changed format (e.g. localized output, a new banner line, or a version that prints 'tab id: 7' instead of 'tab 7'). Also when browse exits 0 but prints an error notice to stdout instead of opening a tab.

Common situations: A browse build from a different branch that changed the newtab CLI output; non-English locale altering the word 'tab'; a daemon in a bad state that prints a diagnostic to stdout but returns 0; a stdin/stdout capture that strips the relevant line.

Related errors


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