JuliusBrussee/caveman · error

at least one sink id is required

Error message

at least one sink id is required

What it means

simulateLearnPlan requires at least one sink id after deduplication of the requested selection. An empty selection means there is nothing to simulate, so it returns an explicit error instead of an empty simulation report. The check happens after collecting unique ids, so passing duplicates that all dedupe to nothing still triggers it.

Solutions

  1. Pass at least one valid sink id to the simulation call
  2. Default the CLI to the full plan when no explicit selection is given
  3. Guard your script: exit early with usage help if the selection list is empty

Example fix

// before
ids := os.Getenv("SINKS")
store.BuildLearnSimulationFiltered(plan, strings.Split(ids, ","))
// after
ids := os.Getenv("SINKS")
if strings.TrimSpace(ids) == "" {
    return fmt.Errorf("set SINKS to at least one sink id")
}
store.BuildLearnSimulationFiltered(plan, strings.Split(ids, ","))
Defensive patterns

Strategy: validation

Validate before calling

cleaned := make([]string, 0, len(ids))
for _, id := range ids {
    if id = strings.TrimSpace(id); id != "" { cleaned = append(cleaned, id) }
}
if len(cleaned) == 0 { return fmt.Errorf("supply at least one sink id to simulate") }

Prevention

When it happens

Trigger: Calling BuildLearnSimulationFiltered with an empty or nil sink id slice, or with a selection that contains no valid entries after dedup.

Common situations: Scripting the CLI with a variable that ends up empty; passing only whitespace/blank ids; wiring automation that forwards an optional flag with no value.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at proxy/internal/store/learn_simulate.go:73

	for _, sink := range plan.Sinks {
		byID[sink.SinkID] = sink
	}
	unique := map[string]bool{}
	for _, id := range sinkIDs {
		id = strings.TrimSpace(id)
		if id != "" {
			unique[id] = true
		}
	}
	ids := make([]string, 0, len(unique))
	for id := range unique {
		if _, ok := byID[id]; !ok {
			return LearnSimulation{}, fmt.Errorf("sink %q not found in current learn plan", id)
		}
		ids = append(ids, id)
	}
	if len(ids) == 0 {
		return LearnSimulation{}, fmt.Errorf("at least one sink id is required")
	}
	sort.Strings(ids)

	// One owner per config file/family. Prefer broader measured weight, then sink
	// id, so overlap refusal is stable regardless of CLI argument order.
	owners := map[string]string{}
	for _, id := range ids {
		sink := byID[id]
		key := simulationOverlapKey(sink)
		if key == "" {
			continue
		}
		owner, exists := owners[key]
		if !exists || sink.TokensPerTurn > byID[owner].TokensPerTurn ||
			(sink.TokensPerTurn == byID[owner].TokensPerTurn && id < owner) {
			owners[key] = id
		}
	}

View on GitHub (pinned to 3ee70a1026)