JuliusBrussee/caveman · warning

cannot supersede with empty text

Error message

cannot supersede with empty text

What it means

Supersede rejects empty/whitespace-only replacement text, mirroring Remember's rule: a current memory can never be replaced with a blank one. The check happens before the transaction begins, so no lineage changes occur.

Solutions

  1. Validate/trim the new text before calling Supersede; skip or prompt again when blank.
  2. To remove a memory, use the Forget-style flow instead of superseding with empty text.
  3. Fix the argument passing in CLI usage so the new text is provided.
  4. Guard interactive flows so submit with empty input is disabled.

Example fix

// before
newText := os.Getenv("NEW_TEXT") // may be empty
_, err := store.Supersede(oldID, newText)
// after
newText := strings.TrimSpace(os.Getenv("NEW_TEXT"))
if newText == "" {
    return fmt.Errorf("replacement text required")
}
_, err := store.Supersede(oldID, newText)
Defensive patterns

Strategy: validation

Validate before calling

// Go
func canSupersedeText(t string) bool { return strings.TrimSpace(t) != "" }

Try / catch

if strings.TrimSpace(newText) == "" { return fmt.Errorf("replacement text required") }
_, err := store.Supersede(oldID, newText)
if err != nil { return err }

Prevention

When it happens

Trigger: Calling Store.Supersede(oldID, "") or with whitespace-only text; cmdSupersede invoked where the new-text argument was empty or a shell variable was unset.

Common situations: Scripting where the replacement text variable is unset; UI flows that allow submitting an empty edit box; piping an empty file/stdin as the new text.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at mem/store.go:206

	row := s.db.QueryRow(memorySelect+` WHERE id = ?`, id)
	if err := scanMemory(row, &stored); err != nil {
		return Memory{}, fmt.Errorf("read back memory: %w", err)
	}
	if stored.ValidUntil != nil {
		return Memory{}, fmt.Errorf("memory %s exists only as an expired historical version and cannot be re-remembered as current", stored.ID)
	}
	return stored, nil
}

// Supersede atomically replaces one current memory with a new current version.
// The old row remains durable for history and byte-exact audit, but normal recall
// excludes it. An already-expired or unknown id fails closed.
func (s *Store) Supersede(oldID, newText string) (Memory, error) {
	if strings.TrimSpace(oldID) == "" {
		return Memory{}, fmt.Errorf("supersede requires old memory id")
	}
	if strings.TrimSpace(newText) == "" {
		return Memory{}, fmt.Errorf("cannot supersede with empty text")
	}
	if err := validateMemorySize(newText); err != nil {
		return Memory{}, err
	}
	tx, err := s.db.Begin()
	if err != nil {
		return Memory{}, fmt.Errorf("supersede begin: %w", err)
	}
	defer tx.Rollback()

	var old Memory
	if err := scanMemory(tx.QueryRow(memorySelect+` WHERE id = ? AND valid_until IS NULL`, oldID), &old); err != nil {
		if err == sql.ErrNoRows {
			return Memory{}, fmt.Errorf("memory %s is not current", oldID)
		}
		return Memory{}, fmt.Errorf("read current memory: %w", err)
	}
	if old.Text == newText {

View on GitHub (pinned to 3ee70a1026)