{"record":{"id":"a073adf1a52dff92","repo":"usememos/memos","slug":"attachment-is-missing","errorCode":null,"errorMessage":"attachment is missing","messagePattern":"attachment is missing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"store/attachment.go","lineNumber":281,"sourceCode":"\nfunc (s *Store) getAttachmentStorageCleanupInstanceSetting(ctx context.Context, attachments []*Attachment) (*storepb.InstanceStorageSetting, error) {\n\tfor _, attachment := range attachments {\n\t\tif AttachmentNeedsInstanceStorageSetting(attachment) {\n\t\t\tinstanceStorageSetting, err := s.GetInstanceStorageSetting(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.Wrap(err, \"failed to get instance storage setting\")\n\t\t\t}\n\t\t\treturn instanceStorageSetting, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// ResolveAttachmentS3Driver resolves the storage driver referenced by an S3\n// attachment payload, validating the payload and supplying the instance setting.\nfunc (s *Store) ResolveAttachmentS3Driver(ctx context.Context, attachment *Attachment) (storage.Driver, *storepb.AttachmentPayload_S3Object, error) {\n\tif attachment == nil {\n\t\treturn nil, nil, errors.New(\"attachment is missing\")\n\t}\n\tif attachment.Payload == nil {\n\t\treturn nil, nil, errors.New(\"attachment payload is missing\")\n\t}\n\ts3Object := attachment.Payload.GetS3Object()\n\tif s3Object == nil {\n\t\treturn nil, nil, errors.New(\"S3 object payload is missing\")\n\t}\n\tif s3Object.Key == \"\" {\n\t\treturn nil, nil, errors.New(\"S3 object key is missing\")\n\t}\n\n\tinstanceStorageSetting, err := s.GetInstanceStorageSetting(ctx)\n\tif err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"failed to get instance storage setting\")\n\t}\n\tdriver, err := ResolveStorageDriver(ctx, instanceStorageSetting, s3Object.StorageId, s3Object.S3Config)\n\tif err != nil {","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/store/attachment.go#L263-L299","documentation":"ResolveAttachmentS3Driver guards its inputs in order: a nil *Attachment triggers \"attachment is missing\" before any payload inspection. The function needs a loaded attachment (including its Payload field) to resolve the S3 storage driver for operations like presigned URL generation or blob reads.","triggerScenarios":"Calling ResolveAttachmentS3Driver with a nil attachment pointer, typically after a GetAttachment/FindAttachment lookup that returned nil without checking.","commonSituations":"Handling an attachment ID from a URL that no longer exists and passing the nil result straight to S3 resolution; refactors that moved the nil check out of the caller.","solutions":["Check the GetAttachment result for nil before resolving the S3 driver","Return a not-found error to the caller at the lookup site instead of proceeding","Add a unit test covering nil attachment input"],"exampleFix":"// before\natt, _ := store.GetAttachment(ctx, find)\ndriver, s3obj, err := store.ResolveAttachmentS3Driver(ctx, att)\n// after\natt, err := store.GetAttachment(ctx, find)\nif err != nil {\n    return err\n}\nif att == nil {\n    return status.Error(codes.NotFound, \"attachment not found\")\n}\ndriver, s3obj, err := store.ResolveAttachmentS3Driver(ctx, att)","handlingStrategy":"type-guard","validationCode":"if attachment == nil {\n    return errors.New(\"cannot resolve S3 driver: attachment not loaded\")\n}","typeGuard":"func hasAttachment(a *store.Attachment) bool { return a != nil }","tryCatchPattern":null,"preventionTips":["Always nil-check store lookups before downstream use","Return not-found to clients at the point of lookup, not deep in storage code"],"tags":["store","attachment","s3","nil-check"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}