JuliusBrussee/caveman · error · Error

--json cannot open a browser

Error message

--json cannot open a browser

What it means

Option conflict in parseStatsOptions: --json and --open are incompatible because JSON output goes to stdout while --open requires rendering an HTML report in a browser; the parser refuses to pretend both can happen.

Solutions

  1. Keep only --json when output is consumed programmatically (pipe to jq, write to file)
  2. Keep only --open when a human wants the rendered view (optionally with --plain for terminal)
  3. Split into two invocations if both JSON and a browsable view are needed

Example fix

// before
myapp stats --json --open
// after
myapp stats --json > stats.json
Defensive patterns

Strategy: validation

Validate before calling

if (argv.includes("--json") && argv.includes("--open")) {
  throw new Error("--json and --open are mutually exclusive");
}

Try / catch

try {
  const opts = parseStatsOptions(argv);
} catch (e) {
  if (e.message === "--json cannot open a browser") {
    console.error(e.message); process.exitCode = 2;
  } else throw e;
}

Prevention

When it happens

Trigger: `stats --json --open`; CI scripts that grew a --open flag added by hand; docs/examples mixing the two flags.

Common situations: Automation adding --json for parsing while a developer appends --open to eyeball results; aliasing stats with --open permanently then adding --json in a script.

Understand the failure class

Background: "mutually exclusive" flag errors: what "can't supply both nx and xx", "--raw is not compatible with -i" and "cannot be used with" mean, and how to fix them — this error's family across 29 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/787c82033041e2a5. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/stats-cli.ts:56

    if (seen.has(arg)) throw new Error(`duplicate ${arg}`);
    seen.add(arg);
    if (valueFlags.has(arg)) {
      const value = argv[++i];
      if (!value || value.startsWith("--") || /[\u0000-\u001f\u007f]/u.test(value)) throw new Error(`${arg} requires a value`);
      if (arg === "--days" && (!/^\d+$/u.test(value) || Number(value) < 1 || Number(value) > 3660)) {
        throw new Error("--days must be an integer from 1 to 3660");
      }
      if (arg === "--out") result.out = value;
      else result.filters.push(arg, value);
    } else if (arg === "--json") result.json = true;
    else if (arg === "--plain" || arg === "--no-open") result.plain = true;
    else if (arg === "--open") result.open = true;
    else if (arg === "--all-time") result.filters.push("--days", "0");
    else throw new Error(`unknown option ${arg}`);
  }
  if (seen.has("--days") && seen.has("--all-time")) throw new Error("choose --days or --all-time");
  if (result.plain && result.open) throw new Error("choose --plain or --open");
  if (result.json && result.open) throw new Error("--json cannot open a browser");
  return result;
}

type StatsMetrics = {
  requests: number;
  successful_requests: number;
  failed_requests: number;
  input_tokens: number;
  output_tokens: number;
  cache_read_tokens: number;
  cache_write_tokens: number;
  complete_usage_requests: number;
  partial_usage_requests: number;
  measured_requests: number;
  unmeasured_requests: number;
  before_tokens: number;
  after_tokens: number;
  saved_tokens: number;

View on GitHub (pinned to 3ee70a1026)