JuliusBrussee/caveman · error

unknown learn subcommand

Error message

unknown learn subcommand: %s

What it means

Thrown by runLearn's default branch when the first argument is not one of scan|report|savings|apply|applied|simulate. Note `learn` defaults to `scan` when no subcommand is given, so this only fires for a non-empty unrecognized subcommand.

Solutions

  1. Use one of: `caveman-proxy learn scan|report|savings|apply|applied|simulate`.
  2. Run bare `caveman-proxy learn` — it defaults to `scan` and shows what the scan path does.
  3. Check spelling, especially apply vs applied which are both valid but mean different things.

Example fix

// before
caveman-proxy learn analyze
// after
caveman-proxy learn scan
Defensive patterns

Strategy: validation

Validate before calling

// shell
case "$SUB" in scan|report|savings|apply|applied|simulate) ;; *) echo "learn subcommands: scan|report|savings|apply|applied|simulate" >&2; exit 2;; esac
caveman-proxy learn "$SUB" "$@"

Prevention

When it happens

Trigger: `caveman-proxy learn analyze`, `learn optimise` (typo), `learn list`, or any misspelling of the six valid subcommands.

Common situations: Guessing subcommand names (`learn suggestions`, `learn fixes`); mixing up singular/plural (`apply` vs `applied` are distinct subcommands); scripts ported from an older CLI layout.

Understand the failure class

Background: "Unknown argument", "Invalid value", and "must be one of": invalid CLI argument errors explained — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

			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,
}

func learnSinkPositionals(args []string) []string {
	var ids []string
	for i := 0; i < len(args); i++ {
		arg := args[i]
		if strings.HasPrefix(arg, "--") {
			if !strings.Contains(arg, "=") && positionalValueFlags[arg] && i+1 < len(args) {

View on GitHub (pinned to 3ee70a1026)