{"record":{"id":"3c24046380303a8e","repo":"cloudreve/cloudreve","slug":"failed-to-delete-direct-links-of-files-v-w","errorCode":null,"errorMessage":"failed to delete direct links of files %v: %w","messagePattern":"failed to delete direct links of files (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"inventory/file.go","lineNumber":596,"sourceCode":"\thardDeleteCtx := schema.SkipSoftDelete(ctx)\n\tfileGroups, chunks := f.batchInCondition(intsets.MaxInt, 10, 1,\n\t\tlo.Map(files, func(file *ent.File, index int) int {\n\t\t\treturn file.ID\n\t\t}),\n\t)\n\n\tfor i, group := range fileGroups {\n\t\t// 4. Delete shares/metadata/directlinks if needed;\n\t\tif _, err := f.client.Share.Delete().Where(share.HasFileWith(group)).Exec(ctx); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to delete shares of files %v: %w\", group, err)\n\t\t}\n\n\t\tif _, err := f.client.Metadata.Delete().Where(metadata.FileIDIn(chunks[i]...)).Exec(schema.SkipSoftDelete(ctx)); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to delete metadata of files %v: %w\", group, err)\n\t\t}\n\n\t\tif _, err := f.client.DirectLink.Delete().Where(directlink.FileIDIn(chunks[i]...)).Exec(hardDeleteCtx); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to delete direct links of files %v: %w\", group, err)\n\t\t}\n\n\t\t// 5. Delete files.\n\t\tif _, err := f.client.File.Delete().Where(group).Exec(hardDeleteCtx); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to delete files %v: %w\", group, err)\n\t\t}\n\t}\n\n\treturn toBeRecycled, storageReduced, nil\n}\n\nfunc (f *fileClient) Copy(ctx context.Context, args *CopyParameter) (map[int][]*ent.File, StorageDiff, error) {\n\tfiles := args.Files\n\tdstMap := args.DstMap\n\tpageSize := capPageSize(f.maxSQlParam, intsets.MaxInt, 10)\n\t// 1. Copy files and metadata\n\tcopyFileStm := lo.Map(files, func(file *ent.File, index int) *ent.FileCreate {\n","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/cloudreve/cloudreve/blob/20c95ad73f3a8bcb72887fea91ff31ab24fa1011/inventory/file.go#L578-L614","documentation":"Wraps a failure to hard-delete direct-link rows attached to the files being removed (DirectLink.Delete().Where(directlink.FileIDIn(...)) under SkipSoftDelete). Direct links are signed URLs bypassing shares; they must be physically removed with the file.","triggerScenarios":"Concurrent creation or use of a direct link on a file being deleted (lock contention); FK RESTRICT from customized schema; connection/context failure mid-cascade.","commonSituations":"Download via direct link racing file deletion; large-batch deletes crossing a DB restart; schema customization adding FKs to the direct_links table.","solutions":["Unwrap the underlying cause; retry deadlock-class failures","Re-invoke the delete to finish remaining batches","Review any schema-level FKs added to direct_links","Tolerate 404-style outcomes in link consumers when a file is removed mid-download"],"exampleFix":"// before\ninv.Delete(ctx, files, props)\n\n// after\nif err := deleteAll(files); isDeadlockOrConnReset(err) {\n    // links are removed per batch; second pass completes the remainder\n    err = deleteAll(files)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, _, err := inv.Delete(ctx, files, props); err != nil {\n    log.WithError(err).Warn(\"direct-link purge failed; rerunning delete to finish\")\n    if _, err2 := inv.Delete(ctx, remainingFiles(ctx, files), props); err2 != nil {\n        return errors.Join(err, err2)\n    }\n}","preventionTips":["Expect link consumers to 404 once the source file deletion begins","Re-run deletes to complete link removal","Audit custom FKs on direct_links"],"tags":["go","ent","database","cascade","direct-link","hard-delete"],"backgroundTag":null,"analyzedSha":"20c95ad73f3a8bcb72887fea91ff31ab24fa1011","analyzedAt":"2026-08-16T01:42:55.403Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}