JuliusBrussee/caveman · error

read memory

Error message

read memory %s: %w

What it means

In memoryByID, any scan/query error other than sql.ErrNoRows is wrapped as "read memory <id>: %w". This distinguishes real read failures (corruption, lock, schema drift) from the benign not-found case, preserving the underlying cause for diagnosis.

Solutions

  1. Read the wrapped %w error to find the actual SQLite/scan failure.
  2. Reopen the store so migrations run if the schema is from an older version.
  3. Wait for or eliminate the competing writer holding the DB lock.
  4. If corruption is indicated, restore the database from backup.

Example fix

// before
m, err := store.History(id)
// after
if err != nil {
    if !strings.Contains(err.Error(), "not found") {
        log.Printf("read memory %s failed: %v", id, err) // inspect wrapped cause
    }
    return err
}
Defensive patterns

Strategy: try-catch

Try / catch

m, err := store.History(id)
if err != nil {
    if strings.Contains(err.Error(), "read memory") {
        log.Printf("underlying cause: %v", errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: Store.History(id) with an existing ID but a failing underlying read: DB locked, corrupted, disk error, or a scanMemory type/conversion failure caused by schema drift (e.g. pre-migration rows with unexpected NULLs).

Common situations: Opening a database written by an older cavemem version before migrateMemorySchema completed, or reading while another process holds a long write transaction.

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/97a12050dc7e4aab. Report an issue: GitHub.

Appendix: source

Thrown at mem/store.go:649

		&supersededBy,
	); err != nil {
		return err
	}
	if validUntil.Valid {
		memory.ValidUntil = &validUntil.String
	}
	memory.Supersedes = supersedes.String
	memory.SupersededBy = supersededBy.String
	return nil
}

func (s *Store) memoryByID(id string) (Memory, error) {
	var memory Memory
	if err := scanMemory(s.db.QueryRow(memorySelect+` WHERE id = ?`, id), &memory); err != nil {
		if err == sql.ErrNoRows {
			return Memory{}, fmt.Errorf("memory %s not found", id)
		}
		return Memory{}, fmt.Errorf("read memory %s: %w", id, err)
	}
	return memory, nil
}

// migrateMemorySchema upgrades pre-supersession stores in place. SQLite cannot
// add several columns in one statement, so each missing column is added
// independently and legacy created_at becomes valid_from.
func migrateMemorySchema(db *sql.DB) error {
	rows, err := db.Query(`PRAGMA table_info(memories)`)
	if err != nil {
		return err
	}
	columns := map[string]bool{}
	for rows.Next() {
		var cid int
		var name, kind string
		var notNull, primaryKey int
		var defaultValue any

View on GitHub (pinned to 3ee70a1026)