JuliusBrussee/caveman · error
read applied fix id
Error message
read applied fix id: %w
What it means
After a successful INSERT, RecordAppliedFix calls result.LastInsertId() to get the new row's ID. If the driver cannot report it the error is wrapped as "read applied fix id". SQLite via mattn/go-sqlite3 or modernc supports this, so failure indicates a driver/connection-level problem.
Solutions
- Verify the registered driver supports LastInsertId for INSERT (use mattn/go-sqlite3 or modernc.org/sqlite)
- Read the wrapped %w error to confirm the driver-level cause
- If the driver can't provide it, query last_insert_rowid() in the same connection instead
Example fix
// before
id, err := result.LastInsertId()
// after
var id int64
err = s.db.QueryRow("SELECT last_insert_rowid()").Scan(&id) Defensive patterns
Strategy: try-catch
Validate before calling
// ensure driver supports LastInsertId: use modernc.org/sqlite or github.com/mattn/go-sqlite3
Try / catch
if _, err := store.RecordAppliedFix(ctx, sink, kind, note); err != nil { if strings.Contains(err.Error(), "read applied fix id") { log.Error("driver lacks LastInsertId support; check sqlite driver") } } Prevention
- Pin a known-good SQLite driver that implements LastInsertId
- Don't interpose generic SQL drivers/proxies over the store DB
- Add an integration test that records a fix and asserts Recorded==true with a positive ID
When it happens
Trigger: LastInsertId() returning an error on the just-executed INSERT result — typically with a driver/pool that doesn't support LastInsertId (e.g. some proxies or non-SQLite drivers behind database/sql).
Common situations: Swapping the SQLite driver for one lacking LastInsertId support; using the result after the connection was reset.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/d4b59563ff47e87b.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/internal/store/learn_outcomes.go:114
// `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
}
if snapshot := newestLearnSnapshotSink(sinkID, cutoff); snapshot != nil {
return snapshot, "report_snapshot"
}
if live != nil {View on GitHub (pinned to 3ee70a1026)