JuliusBrussee/caveman · error

expire old memory

Error message

expire old memory: %w

What it means

Supersede failed while expiring the old memory (UPDATE ... SET valid_until, superseded_by WHERE id = ? AND valid_until IS NULL). This UPDATE is the second half of the lineage swap; failure here aborts the transaction so the store never ends up with a replacement but no expired predecessor.

Solutions

  1. Unwrap the error to identify the driver-level cause.
  2. Verify disk space, file permissions, and that the file is not read-only.
  3. Enable busy_timeout and retry on SQLITE_BUSY.
  4. Ensure the store schema is current for this library version.

Example fix

// before
_, err := store.Supersede(oldID, text)
// after
_, err := store.Supersede(oldID, text)
if err != nil && strings.Contains(err.Error(), "expire old memory") {
	log.Printf("supersede update failed, cause: %v", errors.Unwrap(err))
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(storePath); err != nil || isReadOnly(storePath) { return err }

Try / catch

_, err := store.Supersede(oldID, text)
if err != nil && strings.Contains(err.Error(), "expire old memory") {
	log.Printf("expire step failed, cause: %v", errors.Unwrap(err))
	return err
}

Prevention

When it happens

Trigger: Calling Store.Supersede(oldID, newText) when the UPDATE on the old memory row fails: database locked, disk I/O error, or schema mismatch on the valid_until/superseded_by columns.

Common situations: SQLite file on full disk; long-held write lock from another connection; database migrated from an older schema without the supersession columns.

Related errors


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

Appendix: source

Thrown at mem/store.go:251

		return Memory{}, fmt.Errorf("replacement memory %s already exists", newID)
	}
	now := time.Now().UTC().Format(time.RFC3339Nano)
	if _, err := tx.Exec(
		`INSERT INTO memories
		   (id, text, created_at, valid_from, supersedes)
		 VALUES (?, ?, ?, ?, ?)`,
		newID, newText, now, now, oldID,
	); err != nil {
		return Memory{}, fmt.Errorf("insert replacement memory: %w", err)
	}
	res, err := tx.Exec(
		`UPDATE memories
		    SET valid_until = ?, superseded_by = ?
		  WHERE id = ? AND valid_until IS NULL`,
		now, newID, oldID,
	)
	if err != nil {
		return Memory{}, fmt.Errorf("expire old memory: %w", err)
	}
	if n, _ := res.RowsAffected(); n != 1 {
		return Memory{}, fmt.Errorf("memory %s changed during supersede", oldID)
	}
	if err := tx.Commit(); err != nil {
		return Memory{}, fmt.Errorf("supersede commit: %w", err)
	}
	return Memory{
		ID:         newID,
		Text:       newText,
		CreatedAt:  now,
		ValidFrom:  now,
		Supersedes: oldID,
	}, nil
}

func validateMemorySize(text string) error {
	if len(text) > MaxMemoryBytes {

View on GitHub (pinned to 3ee70a1026)