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
- Run the store migrations so applied_fixes exists before recording
- Check the wrapped %w error for the SQLite cause (no such table, busy, readonly)
- Verify the DB file and its directory are writable and not on a read-only mount
- 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
- Run store migrations on startup before any writes
- Set a busy_timeout on the SQLite connection
- Check DB file writability when running in containers
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)