usememos/memos · error

Internal

Internal

Error message

invalid uid

What it means

Store.CreateAttachment validates the attachment UID against base.UIDMatcher (^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?$) before delegating to the DB driver. The error means create.UID is empty, longer than 36 chars, or contains characters other than alphanumerics and inner hyphens. It fails fast before any SQL runs.

Source

Thrown at store/attachment.go:92

const (
	thumbnailCacheFolder = ".thumbnail_cache"
	motionCacheFolder    = ".motion_cache"
)

type deleteAttachmentStorageFailpointKey struct{}

// ErrDeleteAttachmentStorageFailpoint is returned by the test-only attachment storage failpoint.
var ErrDeleteAttachmentStorageFailpoint = errors.New("delete attachment storage failpoint")

// WithDeleteAttachmentStorageFailpoint forces DeleteAttachmentStorage to return a failpoint error.
func WithDeleteAttachmentStorageFailpoint(ctx context.Context) context.Context {
	return context.WithValue(ctx, deleteAttachmentStorageFailpointKey{}, true)
}

func (s *Store) CreateAttachment(ctx context.Context, create *Attachment) (*Attachment, error) {
	if !base.UIDMatcher.MatchString(create.UID) {
		return nil, errors.New("invalid uid")
	}
	return s.driver.CreateAttachment(ctx, create)
}

func (s *Store) ListAttachments(ctx context.Context, find *FindAttachment) ([]*Attachment, error) {
	// Set default limits to prevent loading too many attachments at once
	shouldApplyDefaultLimit := find.Limit == nil && find.MemoID == nil && len(find.MemoIDList) == 0 && !find.SkipDefaultLimit
	if shouldApplyDefaultLimit && find.GetBlob {
		// When fetching blobs, we should be especially careful with limits
		defaultLimit := 10
		find.Limit = &defaultLimit
	} else if shouldApplyDefaultLimit {
		// Even without blobs, let's default to a reasonable limit
		defaultLimit := 100
		find.Limit = &defaultLimit
	}

	return s.driver.ListAttachments(ctx, find)

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Generate UIDs as short alphanumeric strings, e.g. uuid.NewString() with hyphens removed/encoded or a nanoid restricted to [a-zA-Z0-9-]
  2. Validate the UID with the same regex before calling CreateAttachment
  3. Trim and re-check length (1-36 chars) if UIDs come from user input

Example fix

// before
uid := rawFilename // "my file (1).png"
// after
uid := shortuuid.New() // alphanumeric, matches ^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?$
Defensive patterns

Strategy: validation

Validate before calling

uidRe := regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?$`)
if !uidRe.MatchString(att.UID) {
    return fmt.Errorf("invalid attachment uid %q", att.UID)
}
_, err := store.CreateAttachment(ctx, att)

Type guard

func isValidUID(uid string) bool {
    return base.UIDMatcher.MatchString(uid)
}

Prevention

When it happens

Trigger: CreateAttachment called with an unset UID, a UID starting or ending with '-', exceeding 36 characters, or containing '_', '.', '/', spaces, or other special characters.

Common situations: Using a UUID with dashes at the edges or underscores instead of hyphens; passing a filename as UID; generating IDs with a random-string helper that includes symbols; forgetting to set UID before calling the store.

Related errors


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