JuliusBrussee/caveman · error · Error

choose --plain or --open

Error message

choose --plain or --open

What it means

Thrown by parseStatsOptions, the hand-rolled argv validator for the `stats` command, when the caller fails to pick exactly one output mode: `--plain` and `--open` are a mutually exclusive choice — both together or neither is invalid. It is a sentinel validation error raised before any stats are computed, so the caller gets usage guidance instead of an ambiguous output target.

Solutions

  1. Drop one of the two flags — keep --open for browser output or --plain/--no-open for terminal output
  2. Audit shell aliases/functions that add --no-open or --open automatically
  3. Choose one mode explicitly in scripts instead of concatenating flag arrays

Example fix

// before
myapp stats --plain --open
// after
myapp stats --open
Defensive patterns

Strategy: validation

Validate before calling

const open = argv.includes("--open");
const plain = argv.includes("--plain") || argv.includes("--no-open");
if (open && plain) throw new Error("pass --open or --plain, not both");

Try / catch

try {
  const opts = parseStatsOptions(argv);
} catch (e) {
  if (e.message === "choose --plain or --open") {
    console.error(e.message); process.exitCode = 2;
  } else throw e;
}

Prevention

When it happens

Trigger: `stats --plain --open`; an alias or wrapper that injects --no-open while the user appends --open.

Common situations: Shell aliases that default to one mode; scripts copying flag lists between invocations; users toggling output style without removing the previous flag.

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/52e61f542acf7816. Report an issue: GitHub.

Appendix: source

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

    const arg = argv[i]!;
    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;

View on GitHub (pinned to 3ee70a1026)