JuliusBrussee/caveman · error

usage: caveman-proxy learn simulate

Error message

usage: caveman-proxy learn simulate <sink_id> [<sink_id>...] [--sources <list>] [--since <window>]

What it means

Thrown by runLearn's `simulate` branch when learnSinkPositionals(args) yields no ids: `learn simulate <sink_id> [<sink_id>...] [--sources <list>] [--since <window>]`. Simulation evaluates estimated savings for one or more sinks, so at least one positional id is required.

Solutions

  1. Pass at least one bare sink id: `caveman-proxy learn simulate <sink_id>`.
  2. Pass multiple ids space-separated after the subcommand, flags can come before or after: `learn simulate id1 id2 --sources claude --since 30d`.
  3. Verify the id list is non-empty in scripts before calling the CLI.

Example fix

// before
caveman-proxy learn simulate --sources claude
// after
caveman-proxy learn simulate context-overload-7f3a pixel-sink-9b1c --sources claude --since 30d
Defensive patterns

Strategy: validation

Validate before calling

// shell
IDS=$(learnSinkPositionals "$@")   # bare non-flag args only
if [ -z "$IDS" ]; then echo "learn simulate needs at least one sink_id" >&2; exit 2; fi
caveman-proxy learn simulate $IDS --sources claude --since 30d

Prevention

When it happens

Trigger: `caveman-proxy learn simulate` bare; `learn simulate --sources claude --since 30d` where all args are flags; accidentally quoting ids into the flag values so no bare positionals remain.

Common situations: Batch-simulating sinks in a pipeline where the id list variable expanded to empty; forgetting that flag values like `--sources claude` are skipped by learnSinkPositionals and only bare words count as ids.

Understand the failure class

Background: "no subcommand specified" and "... is required": CLI errors when a required argument is missing — this error's family across 13 libraries.

Related errors


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

Appendix: source

Thrown at proxy/cmd/caveman-proxy/main.go:753

		applyLearnSink(logger, home, sinkID, hasArg(args, "--dry-run"), plan)
	case "applied":
		sinkID := firstPositional(args)
		if sinkID == "" {
			fatalJSON(logger, fmt.Errorf("usage: caveman-proxy learn applied <sink_id> [--fix-kind <k>] [--note <s>]"))
		}
		plan, err := spend.BuildLearnPlanFilteredWithRetro(cwd, sources, since, store.RetroOptions{}, repoFilter)
		if err != nil {
			fatalJSON(logger, err)
		}
		recorded, err := spend.RecordAppliedFix(plan, sinkID, argFlag(args, "--fix-kind", ""), argFlag(args, "--note", ""))
		if err != nil {
			fatalJSON(logger, err)
		}
		printJSON(recorded)
	case "simulate":
		sinkIDs := learnSinkPositionals(args)
		if len(sinkIDs) == 0 {
			fatalJSON(logger, fmt.Errorf("usage: caveman-proxy learn simulate <sink_id> [<sink_id>...] [--sources <list>] [--since <window>]"))
		}
		simulation, err := spend.BuildLearnSimulationFiltered(cwd, sources, since, sinkIDs, repoFilter)
		if err != nil {
			fatalJSON(logger, err)
		}
		printJSON(simulation)
	default:
		fatalJSON(logger, fmt.Errorf("unknown learn subcommand: %s", sub))
	}
}

var positionalValueFlags = map[string]bool{
	"--agent": true, "--behavior-budget-ms": true, "--build": true,
	"--command": true, "--decision": true, "--exit-code": true,
	"--fix-kind": true, "--note": true, "--out": true, "--path": true,
	"--plan": true, "--port": true, "--recent": true, "--repo": true,
	"--retro-budget-ms": true, "--session": true, "--since": true,
	"--sources": true, "--trial-id": true, "--write-report-token": true,

View on GitHub (pinned to 3ee70a1026)