JuliusBrussee/caveman · error · ErrBudgetExceeded

cave_ccr_budget_exceeded

cave_ccr_budget_exceeded

Error message

ccr put: %w

What it means

put writes a recovery record into the CCR sqlite store; if the INSERT fails with a 'database or disk is full' condition (isFull), the error is wrapped as ccr put: %w around ErrBudgetExceeded. The code cave_ccr_budget_exceeded marks this as a budget exhaustion: the PRAGMA max_page_count budget set by configureStorageBudget has been reached and the new record is refused before publishing lossy data.

Solutions

  1. Handle errors.Is(err, ccr.ErrBudgetExceeded) and evict/GC old records before retrying the put
  2. Increase the storage budget (WithMaxStorageBytes / config) or delete old recovery records
  3. Run a cleanup/compaction (VACUUM after deleting) to return pages to the free list
  4. Check actual disk free space; SQLITE_FULL can also mean the disk itself is full

Example fix

// before
handle, err := store.Put(rec) // fails once budget hit
// after
handle, err := store.Put(rec)
if errors.Is(err, ccr.ErrBudgetExceeded) {
    store.PruneOldRecords(cutoff) // free budget
    handle, err = store.Put(rec)
}
Defensive patterns

Strategy: try-catch

Validate before calling

used, _ := store.StorageUsed() // if exposed
if used > 0.9*budget { store.PruneOldRecords(cutoff) }

Try / catch

handle, err := store.Put(rec)
if errors.Is(err, ccr.ErrBudgetExceeded) {
    if gcErr := store.PruneOldRecords(time.Now().AddDate(0, 0, -7)); gcErr != nil { return gcErr }
    handle, err = store.Put(rec)
}

Prevention

When it happens

Trigger: Storing recovery records that grow the database past maxBytes (max_page_count); long-running sessions accumulating many large records; a budget configured smaller than the working set of records being put.

Common situations: Long agent/engine sessions with heavy context accumulation filling the store; after lowering the budget config on an existing populated database; disk-level fullness surfacing as SQLITE_FULL inside the budget cap.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

Thrown at engine/ccr/store_sqlite.go:501

		`INSERT INTO recoveries (handle, created_at, content_type, compressor, tokens_before, tokens_after, original, metadata)
		 VALUES (?,?,?,?,?,?,?,?)
		 ON CONFLICT(handle) DO UPDATE SET
		   created_at=excluded.created_at,
		   content_type=CASE WHEN excluded.tokens_before = 0 AND recoveries.tokens_before > 0 THEN recoveries.content_type ELSE excluded.content_type END,
		   compressor=CASE WHEN excluded.tokens_before = 0 AND recoveries.tokens_before > 0 THEN recoveries.compressor ELSE excluded.compressor END,
		   tokens_before=CASE WHEN excluded.tokens_before = 0 AND recoveries.tokens_before > 0 THEN recoveries.tokens_before ELSE excluded.tokens_before END,
		   tokens_after=CASE WHEN excluded.tokens_before = 0 AND recoveries.tokens_before > 0 THEN recoveries.tokens_after ELSE excluded.tokens_after END,
		   original=excluded.original,
		   metadata=CASE
		     WHEN recoveries.metadata IS NULL OR length(recoveries.metadata) = 0 THEN excluded.metadata
		     ELSE recoveries.metadata
		   END`,
		handle, time.Now().UTC().Format(time.RFC3339Nano),
		rec.ContentType, rec.Compressor, rec.TokensBefore, rec.TokensAfter, rec.Original, rec.Metadata,
	)
	if err != nil {
		if isFull(err) {
			return "", fmt.Errorf("ccr put: %w", ErrBudgetExceeded)
		}
		return "", fmt.Errorf("ccr put: %w", err)
	}
	return handle, nil
}

func (s *Store) recoveryUnchanged(handle string, rec Recovery) (bool, error) {
	var contentType, compressor string
	var tokensBefore, tokensAfter int
	var original, metadata []byte
	err := s.db.QueryRow(
		`SELECT content_type, compressor, tokens_before, tokens_after, original, metadata
		 FROM recoveries WHERE handle=?`,
		handle,
	).Scan(&contentType, &compressor, &tokensBefore, &tokensAfter, &original, &metadata)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}

View on GitHub (pinned to 3ee70a1026)