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
- Drop one of the two flags — keep --open for browser output or --plain/--no-open for terminal output
- Audit shell aliases/functions that add --no-open or --open automatically
- 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
- Pick one presentation mode per invocation; don't concatenate user flags with alias-injected flags
- Audit shell aliases that hardcode --open or --no-open
- Use an explicit env var (e.g. STATS_OUTPUT=json|plain|browser) instead of stacking flags
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
- choose --days or --all-time
- --json cannot open a browser
- caveman-cloud MCP changed after setup; refusing destructive…
- caveman-cloud MCP changed during interrupted setup…
- caveman-cloud MCP changed during interrupted removal…
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)