JuliusBrussee/caveman · error

note exceeds 4096 bytes

Error message

note exceeds 4096 bytes

What it means

The optional free-form note attached to an applied-fix record is persisted in SQLite and echoed in reports. RecordAppliedFix caps it at 4096 bytes to bound row size. Any note longer than that is rejected outright.

Solutions

  1. Truncate the note to 4096 bytes before calling RecordAppliedFix
  2. Move long explanations into a separate file and reference it in a short note
  3. Validate note length in the CLI layer with a friendly message

Example fix

// before
rec, err := store.RecordAppliedFix(ctx, sink, fixKind, longNote)
// after
if len(longNote) > 4096 {
    longNote = longNote[:4096]
}
rec, err := store.RecordAppliedFix(ctx, sink, fixKind, longNote)
Defensive patterns

Strategy: validation

Validate before calling

if len(note) > 4096 { note = note[:4096] } // or reject with a clear message

Try / catch

if _, err := store.RecordAppliedFix(ctx, sink, kind, note); err != nil { if strings.Contains(err.Error(), "note exceeds 4096") { note = note[:4096]; retry } }

Prevention

When it happens

Trigger: Calling RecordAppliedFix with a note argument longer than 4096 bytes.

Common situations: Pasting long analysis text or a full diff into --note; scripting tools that write logs as the note.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at proxy/internal/store/learn_outcomes.go:83

	// A scan run after the user's edit is post-fix evidence. Prefer newest report
	// snapshot strictly older than this plan's scan; use live plan only when no
	// older snapshot contains the sink. Missing live sinks may use snapshot only.
	sink, beforeSource := beforeSinkForAppliedFix(plan, sinkID, liveSink, appliedAtTime)
	if sink == nil {
		return AppliedFixRecord{}, fmt.Errorf("sink %q not found in current learn plan", sinkID)
	}
	fixKind = strings.TrimSpace(fixKind)
	if fixKind == "" {
		fixKind = defaultFixKind(*sink)
	}
	if fixKind == "" {
		return AppliedFixRecord{}, fmt.Errorf("sink %q has no measurable fix kind; pass --fix-kind explicitly", sinkID)
	}
	if len(fixKind) > 128 {
		return AppliedFixRecord{}, fmt.Errorf("fix kind exceeds 128 bytes")
	}
	if len(note) > 4096 {
		return AppliedFixRecord{}, fmt.Errorf("note exceeds 4096 bytes")
	}
	appliedAt := appliedAtTime.Format(time.RFC3339)
	beforeEvidence := make(map[string]any, len(sink.Evidence)+1)
	for key, value := range sink.Evidence {
		beforeEvidence[key] = value
	}
	beforeEvidence["before_source"] = beforeSource
	evidence, err := json.Marshal(beforeEvidence)
	if err != nil {
		return AppliedFixRecord{}, fmt.Errorf("encode before evidence: %w", err)
	}
	// Fingerprint the edited artifact as it stands NOW — which at
	// `caveman learn applied` time is immediately after the approved edit. This
	// is the provenance anchor: a later scan re-hashes the same path and can
	// state whether the change we proposed is still the change that is there.
	target := fingerprintFixTarget(*sink)
	targetJSON := ""
	if target != nil {

View on GitHub (pinned to 3ee70a1026)