{"record":{"id":"ac37981ff493464c","repo":"AlistGo/alist","slug":"access-limit-must-be-0-or-greater","errorCode":null,"errorMessage":"access_limit must be 0 or greater","messagePattern":"access_limit must be 0 or greater","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/handles/share.go","lineNumber":252,"sourceCode":"\t\t}\n\t\tif exists {\n\t\t\treturn \"\", errShareIDExists\n\t\t}\n\t\treturn shareID, nil\n\t}\n\texists, err := db.ShareIDExistsExceptID(shareID, excludeID)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"check share id availability: %w\", err)\n\t}\n\tif exists {\n\t\treturn \"\", errShareIDExists\n\t}\n\treturn shareID, nil\n}\n\nfunc normalizeShareAccessLimit(accessLimit int64, burnAfterRead *bool) (int64, bool, error) {\n\tif accessLimit < 0 {\n\t\treturn 0, false, fmt.Errorf(\"access_limit must be 0 or greater\")\n\t}\n\tif accessLimit == 0 && burnAfterRead != nil && *burnAfterRead {\n\t\taccessLimit = 1\n\t}\n\treturn accessLimit, accessLimit == 1, nil\n}\n\nfunc parseShareExpireAt(raw string) (*time.Time, error) {\n\tvalue := strings.TrimSpace(raw)\n\tif value == \"\" {\n\t\treturn nil, nil\n\t}\n\tif parsed, err := time.Parse(time.RFC3339, value); err == nil {\n\t\treturn &parsed, nil\n\t}\n\tlayouts := []string{\n\t\t\"2006-01-02T15:04:05\",\n\t\t\"2006-01-02T15:04\",","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/handles/share.go#L234-L270","documentation":"Returned by normalizeShareAccessLimit (server/handles/share.go:252) when a share create/update request carries a negative access_limit. access_limit is the number of times a share link may be accessed before it stops working; 0 means unlimited, and when burn_after_read is true a value of 0 is promoted to 1 so the link dies after the first read. Negative values have no defined meaning, so they are rejected outright.","triggerScenarios":"POST/PUT to the share create/update endpoints with a JSON body containing \"access_limit\": -1 or any negative number; a client computing access_limit as limit - used that underflows after concurrent downloads of the same share.","commonSituations":"Clients that use -1 as an 'unlimited' sentinel (this API uses 0 for unlimited); frontend forms defaulting the field to -1; decrement logic on a counter shared between requests.","solutions":["Send access_limit: 0 — that is the 'unlimited' value in this API","Clamp any client-side computed remaining-count to 0 before submitting","Add a >= 0 check in the request builder so negative values never leave the client"],"exampleFix":"// before\nbody := {\"path\": \"/data/file.zip\", \"access_limit\": -1}\n// after\nbody := {\"path\": \"/data/file.zip\", \"access_limit\": 0}","handlingStrategy":"validation","validationCode":"func validAccessLimit(v int64) bool { return v >= 0 }\n// before building the request:\nif !validAccessLimit(req.AccessLimit) { return fmt.Errorf(\"access_limit must be >= 0 (0 = unlimited)\") }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat 0, not -1, as 'unlimited' in all share clients","Clamp computed remaining-access counts to 0","Unit-test the request builder with boundary values 0 and -1"],"tags":["share","validation","api"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}