You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I understand this issue needs status:approved before a PR can be opened
📝 Bug Description
Prompt capture accepts empty content, but the cloud server rejects that same payload at push time with 500: prompt payload content is required for upsert. Because the push queue is sequential and global across projects, a single empty-prompt mutation permanently stalls cloud push for every project until repaired by hand. In our case one empty prompt captured on 2026-07-22 silently blocked ~500 pending mutations across 4 projects for 8 days (174 consecutive failures), while pull kept working — so devices that were already mirrored reported a healthy sync and the data loss was invisible from their side.
Three gaps combine into this:
No capture-time validation.handleSavePrompt (internal/mcp/mcp.go:1564) and Store.AddPrompt (internal/store/store.go:2549) accept and persist content="" verbatim, and enqueue the upsert mutation. The server enforces content as required (cloudserver/chunkcodec canonicalization), so the mutation is un-pushable from the moment it is created. This is the prompt-entity mirror of Reject empty observation titles before persistence #459 (empty observation titles).
Repair does not support the prompt entity.engram cloud upgrade repair reports manual-action-required and offers nothing; docs/engram-cloud/troubleshooting.md documents loop/interactive repair for entity=session|observation op=upsert only. Relates to Cloud repair should backfill and report legacy mutation required fields #340 (required-field repair), which lists session/observation blocker classes but not prompts.
🔄 Steps to Reproduce
Configure a cloud target and enroll a project (engram cloud enroll <project>).
Save a prompt with empty content (e.g. mem_save_prompt with content: "" — in our case it came from automated hook capture): a user_prompts row and an upsert mutation with "content":"" are created.
Let the daemon push (or run engram sync --cloud --project <project>).
Push fails: mutation push: status 500: insert mutations: cloudstore: canonicalize materialized mutation batch chunk: mutations[N]: prompt payload content is required for upsert.
sync_state goes degraded, consecutive_failures grows unbounded; mutations from all projects behind that seq stop replicating.
engram doctor → sync_mutation_payload_missing_required_fields (correct detection).
Recovery required manual surgery: engram delete prompt <id> (to tombstone the source row) plus a hand-written DELETE FROM sync_mutations WHERE seq=<N> in SQLite.
✅ Expected Behavior
Any of these layers would have prevented or contained it (ordered by depth):
Queue: quarantine a mutation the server permanently rejects (validation-class 500) into a dead-letter state visible in doctor, instead of retrying forever and blocking unrelated projects.
Repair: extend cloud upgrade repair to the prompt entity (drop-or-tombstone flow for empty content), so no manual SQL is needed.
❌ Actual Behavior
Empty prompt persisted and enqueued; push retries the same mutation indefinitely (174 consecutive failures over 8 days); all projects' cloud replication stalls; repair declines with manual-action-required; recovery requires manual SQLite edits.
The stored mutation payload was {"sync_id":"prompt-a1fdc6ceb6b624fb","session_id":"...","content":"","project":"owlvision","created_at":"2026-07-22 13:43:09"} and the source user_prompts row had content='' as well — so this is not payload drift; the empty value was accepted at capture.
📋 Pre-flight Checks
status:approvedbefore a PR can be opened📝 Bug Description
Prompt capture accepts empty
content, but the cloud server rejects that same payload at push time with500: prompt payload content is required for upsert. Because the push queue is sequential and global across projects, a single empty-prompt mutation permanently stalls cloud push for every project until repaired by hand. In our case one empty prompt captured on 2026-07-22 silently blocked ~500 pending mutations across 4 projects for 8 days (174 consecutive failures), while pull kept working — so devices that were already mirrored reported a healthy sync and the data loss was invisible from their side.Three gaps combine into this:
handleSavePrompt(internal/mcp/mcp.go:1564) andStore.AddPrompt(internal/store/store.go:2549) accept and persistcontent=""verbatim, and enqueue the upsert mutation. The server enforcescontentas required (cloudserver/chunkcodec canonicalization), so the mutation is un-pushable from the moment it is created. This is the prompt-entity mirror of Reject empty observation titles before persistence #459 (empty observation titles).engram cloud upgrade repairreportsmanual-action-requiredand offers nothing;docs/engram-cloud/troubleshooting.mddocuments loop/interactive repair forentity=session|observation op=upsertonly. Relates to Cloud repair should backfill and report legacy mutation required fields #340 (required-field repair), which lists session/observation blocker classes but not prompts.🔄 Steps to Reproduce
engram cloud enroll <project>).mem_save_promptwithcontent: ""— in our case it came from automated hook capture): auser_promptsrow and anupsertmutation with"content":""are created.engram sync --cloud --project <project>).mutation push: status 500: insert mutations: cloudstore: canonicalize materialized mutation batch chunk: mutations[N]: prompt payload content is required for upsert.sync_stategoesdegraded,consecutive_failuresgrows unbounded; mutations from all projects behind that seq stop replicating.engram doctor→sync_mutation_payload_missing_required_fields(correct detection).engram cloud upgrade doctor/repair --project <project>→upgrade_blocked_legacy_mutation_manual,manual-action-required: prompt payload missing required upsert fields: content— no repair path offered.Recovery required manual surgery:
engram delete prompt <id>(to tombstone the source row) plus a hand-writtenDELETE FROM sync_mutations WHERE seq=<N>in SQLite.✅ Expected Behavior
Any of these layers would have prevented or contained it (ordered by depth):
doctor, instead of retrying forever and blocking unrelated projects.cloud upgrade repairto thepromptentity (drop-or-tombstone flow for empty content), so no manual SQL is needed.❌ Actual Behavior
Empty prompt persisted and enqueued; push retries the same mutation indefinitely (174 consecutive failures over 8 days); all projects' cloud replication stalls; repair declines with
manual-action-required; recovery requires manual SQLite edits.Operating System
Linux (Other)
Engram Version
1.20.0
Agent / Client
Claude Code
Relevant Logs
Additional Context
{"sync_id":"prompt-a1fdc6ceb6b624fb","session_id":"...","content":"","project":"owlvision","created_at":"2026-07-22 13:43:09"}and the sourceuser_promptsrow hadcontent=''as well — so this is not payload drift; the empty value was accepted at capture.