JuliusBrussee/caveman · error

sink %q not found in current learn plan

Error message

sink %q not found in current learn plan

What it means

applyLearnSink searched plan.Sinks for the given sink_id (exact string match on SinkID) and found nothing. The plan is rebuilt fresh inside this invocation (BuildLearnPlan over the current working directory and since-cutoff), so the id must exist in the plan as computed now, not in an older scan you read from.

Source

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

		Enabled:          hasArg(args, "--retro"),
		BehaviorBudgetMS: behaviorBudget,
		BudgetMS:         retroBudget,
	}
}

// applyLearnSink materializes a candidate edit for a reducible sink under
// ~/.caveman/candidates/ and returns it. It never edits user config files itself —
// the analyzer is read-only; the consent-gated editing skill performs real edits.
func applyLearnSink(logger *slog.Logger, home, sinkID string, dryRun bool, plan store.LearnPlan) {
	var target *store.Sink
	for i := range plan.Sinks {
		if plan.Sinks[i].SinkID == sinkID {
			target = &plan.Sinks[i]
			break
		}
	}
	if target == nil {
		fatalJSON(logger, fmt.Errorf("sink %q not found in current learn plan", sinkID))
	}
	var candidate map[string]any
	switch target.Class {
	case "reducible":
		candidate = map[string]any{
			"sink_id":                        sinkID,
			"title":                          target.Title,
			"class":                          target.Class,
			"expected_tokens_per_turn_saved": target.TokensPerTurn,
			"suggestion":                     target.Suggestion,
			"evidence":                       target.Evidence,
			"net_token_negative_gate":        "the editing skill must measure before>after tokens/turn and reject any edit that does not reduce them",
			"basis":                          plan.Basis,
		}
	case "recurring_context":
		candidate = offloadCandidate(sinkID, target, plan.Basis, plan.Window.Since)
	default:
		printJSON(map[string]any{

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Re-run caveman-proxy learn scan (same cwd and --since you will use for apply) and copy the sink_id exactly from that output
  2. Run learn apply <sink_id> --dry-run with the freshly scanned id to confirm the match before the real run
  3. Keep scan and apply in the same script/cwd so the plan cannot drift between them

Example fix

# before (id from an old scan, different cwd)
cd ~/other && caveman-proxy learn apply old-sink-42

# after (rescan in the target cwd, use current id)
cd ~/project && caveman-proxy learn scan
caveman-proxy learn apply compress-stdin --dry-run
Defensive patterns

Strategy: validation

Validate before calling

PLAN=$(caveman-proxy learn scan --json)
SINK_ID=$(echo "$PLAN" | jq -r '.sinks[0].sink_id')
[ -n "$SINK_ID" ] && caveman-proxy learn apply "$SINK_ID" --dry-run

Prevention

When it happens

Trigger: Passing a sink id from a previous learn scan after the underlying files/config changed and the sink no longer registers; typo'd or case-mismatched id; running apply from a different cwd than the scan (sinks are cwd-relative), or with a different --since window that excludes the sink's evidence.

Common situations: Scan in one directory, apply in another; stale sink list pasted from yesterday's output; --since tightened so the sink's tokens no longer cross the threshold and it drops out of the plan.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/2e63a6d30b5170ce. Report an issue: GitHub.