JuliusBrussee/caveman · error

count memories

Error message

count memories: %w

What it means

Store.Count wraps any error from the SQLite `SELECT COUNT(*) FROM memories WHERE valid_until IS NULL` query with the "count memories" prefix. It exists so callers can distinguish a durable-memory count failure from a legitimate zero and avoid presenting 0 on error. The wrapper preserves the underlying database error via %w.

Solutions

  1. Check that the store was opened successfully and Close() has not been called before Count().
  2. Inspect the wrapped %w error to identify the SQLite failure (corruption, disk full, schema missing).
  3. Reopen or recreate the store so migrations run and the memories table exists.
  4. Retry after resolving contention if the DB was locked by another process.

Example fix

// before
count, err := store.Count()
fmt.Printf("%d memories", count) // prints 0 on error
// after
count, err := store.Count()
if err != nil {
    return fmt.Errorf("memory count unavailable: %w", err)
}
fmt.Printf("%d memories", count)
Defensive patterns

Strategy: try-catch

Validate before calling

if store == nil {
    return errors.New("store not initialized before Count")
}

Try / catch

count, err := store.Count()
if err != nil {
    return fmt.Errorf("memory count unavailable: %w", err) // never treat as 0
}

Prevention

When it happens

Trigger: Calling Store.Count() when the underlying SQLite database is corrupted, locked by another writer, closed, or when the memories table/schema is missing (e.g. store never migrated, file deleted mid-session).

Common situations: Tests like TestCount and TestForgetRepairsHistoryWithoutResurrectingExpiredMemory call Count after store mutations; in production this appears when the DB file is on a full disk, the store was closed before Count, or a schema migration failed leaving no memories table.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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

Appendix: source

Thrown at mem/store.go:377

	if err := tx.Commit(); err != nil {
		return false, fmt.Errorf("forget commit: %w", err)
	}
	return true, nil
}

func nullableLineageID(value sql.NullString) any {
	if value.Valid {
		return value.String
	}
	return nil
}

// Count returns the number of durable memory blocks without loading their
// contents. Callers omit this number on error rather than presenting zero.
func (s *Store) Count() (int, error) {
	var count int
	if err := s.db.QueryRow(`SELECT COUNT(*) FROM memories WHERE valid_until IS NULL`).Scan(&count); err != nil {
		return 0, fmt.Errorf("count memories: %w", err)
	}
	return count, nil
}

// Hit is one recall result: the recalled memory compressed for injection, with
// the inferred token cost, the match score, and a CCR handle to recover the full
// original when the compression dropped detail.
type Hit struct {
	ID             string  `json:"id"`
	Text           string  `json:"text"`
	Score          float64 `json:"score"`
	TokensAdded    int     `json:"tokens_added"`
	Basis          string  `json:"basis"`
	RecoveryHandle string  `json:"recovery_handle,omitempty"`
}

// RecallOptions configures Recall. A zero Threshold means DefaultThreshold; a
// zero Limit means DefaultLimit; a zero TokenBudget means DefaultTokenBudget.

View on GitHub (pinned to 3ee70a1026)