usememos/memos · error · ErrMemoMutationConflict

memo state changed

Error message

memo state changed

What it means

ErrMemoMutationConflict (store/memo_attachment.go:10) is an optimistic-concurrency sentinel returned by Store.ApplyMemoMutation. Inside one transaction, each DB driver (store/db/{sqlite,mysql,postgres}/memo_attachment.go) rechecks the memo's existence, content, and attachment bindings that the API layer validated earlier; if another writer changed them in between, the transaction aborts with this error instead of overwriting concurrent state. The v1 memo attachment service maps it to codes.FailedPrecondition (server/router/api/v1/memo_attachment_service.go:181).

Source

Thrown at store/memo_attachment.go:10

package store

import (
	"context"
	"errors"
)

// ErrMemoMutationConflict indicates that memo or attachment state changed
// after an API request prepared its mutation.
var ErrMemoMutationConflict = errors.New("memo state changed")

// MemoAttachmentBinding describes one attachment that should be bound to a
// memo. WasBoundToMemo distinguishes an existing binding from a new one so the
// driver can reject ownership transfers while preserving legacy rows already
// attached to the memo.
type MemoAttachmentBinding struct {
	ID             int32
	UID            string
	UpdatedTs      int64
	WasBoundToMemo bool
}

// MemoMutation atomically updates a memo, its attachment bindings, and its
// reference relations. Removed attachment rows are detached in the transaction
// and are deleted from storage separately, so a storage failure remains
// retriable.
type MemoMutation struct {
	MemoID                    int32

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Retry the whole operation: re-fetch the memo and attachments, rebuild the mutation from fresh state, and re-apply (the design keeps storage deletions outside the transaction precisely so retries are safe).
  2. Check for the sentinel with errors.Is(err, store.ErrMemoMutationConflict) at the service boundary and surface codes.FailedPrecondition so clients know to refresh rather than blindly repeat.
  3. Serialize concurrent edits client-side (e.g. disable submit while an edit is in flight) to avoid lost-update races.
  4. If it persists for one row, inspect the wrap message ("memo no longer exists", "attachment X is no longer bound") to see which precondition broke.

Example fix

// before
if err := s.Store.ApplyMemoMutation(ctx, mutation); err != nil {
    return status.Errorf(codes.Internal, "failed: %v", err)
}

// after
if err := s.Store.ApplyMemoMutation(ctx, mutation); err != nil {
    if stderrors.Is(err, store.ErrMemoMutationConflict) {
        return status.Errorf(codes.FailedPrecondition, "memo state changed: %v", err)
    }
    return status.Errorf(codes.Internal, "failed to apply memo mutation: %v", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// Re-read the memo and attachments immediately before building the mutation,
// and include ExpectedMemoContent from that fresh read:
// mutation.ExpectedMemoContent = memo.Content (fetched in the same request).

Type guard

func isMemoMutationConflict(err error) bool {
    return errors.Is(err, store.ErrMemoMutationConflict)
}

Try / catch

if err := s.Store.ApplyMemoMutation(ctx, mutation); err != nil {
    if stderrors.Is(err, store.ErrMemoMutationConflict) {
        // reload memo + attachments, rebuild mutation, retry once;
        // surface FailedPrecondition to the client if the retry also conflicts
        return status.Errorf(codes.FailedPrecondition, "memo state changed: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Two concurrent UpdateMemoWithAttachments / SetMemo calls on the same memo; deleting or rebinding an attachment (DetachAttachment, ownership transfer to another memo) between the request's validation read and ApplyMemoMutation; a client retrying a stale request built from an old memo snapshot; memo deleted by another session while the mutation is in flight.

Common situations: Multi-tab or multi-device editing of the same memo, automation/scripts racing with the web UI, mobile clients with offline queues replaying old updates, or background runners touching attachments concurrently with a user edit.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/45593ac32ec4fff2. Report an issue: GitHub.