gastownhall/beads · warning · memoryops.ErrValidation

could not generate key from content; use --key to specify on

Error message

could not generate key from content; use --key to specify one

What it means

ResolveKey could not derive a key from the memory content (DeriveKey returned empty) and the caller supplied no usable explicit key. Since every memory must live under some key, it fails with memoryops.ErrValidation telling the user to pass --key. This happens when the content yields no derivable key material.

Source

Thrown at internal/memoryapi/memoryapi.go:129

	if err := ValidateContent(content); err != nil {
		return "", err
	}
	if key != "" {
		// THE SAME RULE READS AND WRITES USE. ValidateKey — which Recall and
		// Forget run — refuses a key that is empty after trimming, so accepting
		// one here mints a row that no memory operation can ever name again:
		// enumerable by List forever, unrecallable, unforgettable, reachable
		// only through `bd config unset` on the raw storage key. The HTTP
		// surface inherits the same split, so POST would accept what GET and
		// DELETE refuse.
		//
		// Surrounding space is still preserved on keys that survive this: the
		// rule is that a key must NAME something, not that it must be tidy.
		return ValidateKey(key)
	}
	derived := DeriveKey(content)
	if derived == "" {
		return "", fmt.Errorf("%w: could not generate key from content; use --key to specify one", memoryops.ErrValidation)
	}
	return derived, nil
}

// FilterMemories narrows already-prefix-stripped memories by a raw search term.
//
// THE FOLDING IS HERE, on both sides. The shipped `bd memories` lowercased the
// term at the front door and the filter lowercased the stored values, which
// worked only for as long as every caller remembered the first half; a second
// door that passed the term through raw would have matched nothing and reported
// an empty plane. Now the caller passes what the user typed.
//
// The answer is a fresh map, never nil and never the argument: a caller holding
// the full plane must still hold it after asking a narrower question.
func FilterMemories(all map[string]string, search string) map[string]string {
	if search == "" {
		out := make(map[string]string, len(all))
		for k, v := range all {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass an explicit --key when storing the memory.
  2. Ensure the content contains usable text from which a key can be derived.
  3. Handle the ErrValidation by re-prompting for content or key.

Example fix

// before
bd remember "..." // fails: could not generate key
// after
bd remember "..." --key my-note-key
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(content) == "" || (explicitKey != "" && strings.TrimSpace(explicitKey) == "") {
    return fmt.Errorf("provide non-empty content and, if needed, an explicit --key")
}

Try / catch

key, err := memoryapi.ResolveKey(content, explicitKey)
if err != nil {
    if errors.Is(err, memoryops.ErrValidation) {
        return fmt.Errorf("retry with --key set")
    }
    return err
}

Prevention

When it happens

Trigger: Calling ResolveKey with no explicit key and content whose DeriveKey result is ""; ResolveKey with an explicit key that itself fails ValidateKey (blank), leaving the derivation path to fail.

Common situations: Content that DeriveKey cannot reduce to a key (degenerate/whitespace-normalized input); forgetting --key on bd remember for content that resists key derivation; whitespace-only explicit key falling through to derivation.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/fc1bd0604011c9f9. Report an issue: GitHub.