Skip to content

fix(store): reject empty observation titles before persistence#467

Open
tommanzur wants to merge 4 commits into
Gentleman-Programming:mainfrom
tommanzur:fix/459-reject-empty-observation-titles
Open

fix(store): reject empty observation titles before persistence#467
tommanzur wants to merge 4 commits into
Gentleman-Programming:mainfrom
tommanzur:fix/459-reject-empty-observation-titles

Conversation

@tommanzur

Copy link
Copy Markdown

Summary

Closes #459.

Empty (or whitespace-only) observation titles were accepted by mem_save and
store.AddObservation, persisted locally, and queued for cloud sync. The cloud
server rejects them via ValidateSyncMutationPayload, blocking the entire
mutation queue silently — the caller receives success at write time and has no
feedback to retry with a valid title.

This PR pushes the same validation up to the write path, so the failure
surfaces immediately and can be corrected.

Changes

  • internal/store/store.go: AddObservation rejects empty/whitespace titles
    with a clear error, before any DB access. Validated after stripPrivateTags
    so it matches exactly the value that gets persisted and later pushed to the
    cloud server.
  • internal/mcp/mcp.go: handleSave returns an agent-actionable
    ToolResultError before reaching the store, with an example title format so
    the model knows what to provide on retry.
  • Tests added for both layers (empty / whitespace / missing title).

Why this approach

Per the suggestion on #459, reusing the rule already enforced by
ValidateSyncMutationPayload (diagnostic.go) keeps validation in one logical
place. The fix is defensive at two layers — MCP for UX, store as the single
source of truth — so the CLI, MCP, and any future entrypoint inherit it.

Test plan

  • go test ./internal/store/... — new tests for empty/whitespace titles + a positive case
  • go test ./internal/mcp/... — handler returns ToolResultError, nothing persisted
  • go build ./cmd/engram — binary builds clean
  • Existing tests pass (no regression in dedupe, topic_key upsert, normalization)

Notes

  • This is technically a behavior change for callers passing an empty title
    today, but those cases are already broken downstream (cloud sync blocks them).
    The change turns a silent failure into a loud, immediate one.
  • stripPrivateTags replaces <private>…</private> with [REDACTED], not an
    empty string, so a title made only of private tags is not rejected — that
    is intended.

Out of scope

  • Backfilling existing observations with empty titles in users' local DBs. The
    repair tooling and the SQLite workaround documented in Reject empty observation titles before persistence #459's comments handle
    that path. This PR prevents new occurrences.
  • Title length / format constraints beyond non-empty. Can be a follow-up.

tommanzur added 4 commits June 5, 2026 20:11
Empty (or whitespace-only) titles passed to AddObservation were accepted
locally but rejected by the cloud sync server (ValidateSyncMutationPayload),
silently blocking the mutation queue. Validate at write time so the failure
surfaces immediately and the caller gets actionable feedback.

Closes Gentleman-Programming#459
Mirror the existing content check. The error message instructs the agent to
provide a short, searchable title, since the previous silent success caused
stuck cloud sync queues with no feedback loop.

Refs Gentleman-Programming#459
@tommanzur

Copy link
Copy Markdown
Author

Thanks @ricardoarz-dev for the pointer to ValidateSyncMutationPayload — this PR reuses exactly that rule at write time, in store.AddObservation (single source of truth) plus an early agent-actionable error in handleSave. cc @jorocoimbra since you confirmed the repro and the stuck-queue recovery path.

One note for a maintainer: I couldn't add the type:bug label myself (external contributor — GitHub blocks labeling), so it'll need to be applied on triage.

Happy to adjust the approach or split anything further if it helps review.

@Alan-TheGentleman Alan-TheGentleman added the type:bug Bug fix label Jun 13, 2026
@Alan-TheGentleman

Copy link
Copy Markdown
Collaborator

#459 is now approved and this PR has the type:bug label. The problem is valid: empty titles should fail before persistence instead of poisoning sync later. Next step is code review after required checks are green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reject empty observation titles before persistence

2 participants