JuliusBrussee/caveman · error

record applied fix

Error message

record applied fix: %w

What it means

The final INSERT into the applied_fixes table failed; the driver error is wrapped as "record applied fix". This is the durable write of the outcome row, so any SQLite-level problem (schema, constraints, I/O) surfaces here.

Solutions

  1. Run the store migrations so applied_fixes exists before recording
  2. Check the wrapped %w error for the SQLite cause (no such table, busy, readonly)
  3. Verify the DB file and its directory are writable and not on a read-only mount
  4. Retry on SQLITE_BUSY or configure a busy_timeout

Example fix

// before
s.db, _ = sql.Open("sqlite", path) // migrations skipped
// after
s.db, _ = sql.Open("sqlite", path)
if err := s.migrate(ctx); err != nil { return err }
Defensive patterns

Strategy: try-catch

Validate before calling

var exists int; _ = db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='applied_fixes'").Scan(&exists); if exists == 0 { runMigrations() }

Try / catch

if _, err := store.RecordAppliedFix(ctx, sink, kind, note); err != nil { var busy bool; busy = strings.Contains(err.Error(), "SQLITE_BUSY"); if busy { time.Sleep(backoff); retry } else { return err } }

Prevention

When it happens

Trigger: The 8-column INSERT into applied_fixes fails: table missing (store not migrated), constraint violation (e.g. UNIQUE on sink_id+applied_at if defined), disk full, or read-only database file.

Common situations: Opening the store against an old database file without the applied_fixes migration; concurrent writers hitting SQLITE_BUSY; corrupt or locked DB in a container with a read-only mount.

Related errors


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

Appendix: source

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

	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 {
		if encoded, encodeErr := json.Marshal(target); encodeErr == nil {
			targetJSON = string(encoded)
		}
	}
	result, err := s.db.Exec(`INSERT INTO applied_fixes
		(sink_id, practice_id, fix_kind, applied_at, before_tokens_per_turn, before_evidence_json, note, target_json)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, sink.SinkID, sink.PracticeID, fixKind, appliedAt, sink.TokensPerTurn, string(evidence), note, targetJSON)
	if err != nil {
		return AppliedFixRecord{}, fmt.Errorf("record applied fix: %w", err)
	}
	id, err := result.LastInsertId()
	if err != nil {
		return AppliedFixRecord{}, fmt.Errorf("read applied fix id: %w", err)
	}
	return AppliedFixRecord{
		Recorded: true, ID: id, SinkID: sink.SinkID, PracticeID: sink.PracticeID,
		FixKind: fixKind, AppliedAt: appliedAt, BeforeTokensPerTurn: sink.TokensPerTurn,
		BeforeEvidence: beforeEvidence, BeforeSource: beforeSource, Note: note,
		Target: target,
	}, nil
}

func beforeSinkForAppliedFix(plan LearnPlan, sinkID string, live *Sink, fallbackCutoff time.Time) (*Sink, string) {
	cutoff := fallbackCutoff
	if parsed, err := time.Parse(time.RFC3339Nano, plan.computedAt); err == nil {
		cutoff = parsed
	}

View on GitHub (pinned to 3ee70a1026)