{"record":{"id":"b8ea8cee654303fa","repo":"AlistGo/alist","slug":"expire-hours-must-be-0-or-greater","errorCode":null,"errorMessage":"expire_hours must be 0 or greater","messagePattern":"expire_hours must be 0 or greater","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/handles/share.go","lineNumber":286,"sourceCode":"\tlayouts := []string{\n\t\t\"2006-01-02T15:04:05\",\n\t\t\"2006-01-02T15:04\",\n\t\t\"2006-01-02 15:04:05\",\n\t}\n\tfor _, layout := range layouts {\n\t\tif parsed, err := time.ParseInLocation(layout, value, time.Local); err == nil {\n\t\t\treturn &parsed, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"invalid expire_at\")\n}\n\nfunc resolveShareExpireAt(expireAt string, expireHours int64) (*time.Time, error) {\n\tif strings.TrimSpace(expireAt) != \"\" {\n\t\treturn parseShareExpireAt(expireAt)\n\t}\n\tif expireHours < 0 {\n\t\treturn nil, fmt.Errorf(\"expire_hours must be 0 or greater\")\n\t}\n\tif expireHours == 0 {\n\t\treturn nil, nil\n\t}\n\texpires := time.Now().Add(time.Duration(expireHours) * time.Hour)\n\treturn &expires, nil\n}\n\nfunc sharePasswordMatched(share *model.Share, password string) bool {\n\tif !share.HasPassword() {\n\t\treturn true\n\t}\n\thash := sharePasswordHash(password, share.PasswordSalt)\n\treturn subtle.ConstantTimeCompare([]byte(hash), []byte(share.PasswordHash)) == 1\n}\n\nfunc getShareAccessToken(c *gin.Context, fallback string) string {\n\tif fallback != \"\" {","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/handles/share.go#L268-L304","documentation":"Returned by resolveShareExpireAt (server/handles/share.go:286) when the request supplies a negative expire_hours and no explicit expire_at string. expire_hours is a shorthand that computes the expiry timestamp as now + expire_hours; 0 disables expiry. expire_at takes priority and is parsed instead when non-empty, so this error fires only on the hours path.","triggerScenarios":"Share create/update with expire_hours: -1 in the body and an empty/absent expire_at; e.g. curl -d '{\"path\":\"/f\",\"expire_hours\":-24}'.","commonSituations":"Clients using -1 to mean 'never expire' (this API uses 0); negative offsets computed from a target time already in the past.","solutions":["Send expire_hours: 0 to disable expiry","If you computed the hours from a deadline, skip negative results and send 0","Validate the field in the request builder before calling the API"],"exampleFix":"// before\n{\"expire_hours\": -24}\n// after\n{\"expire_hours\": 0}","handlingStrategy":"validation","validationCode":"if req.ExpireHours < 0 { return fmt.Errorf(\"expire_hours must be >= 0 (0 = no expiry)\") }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use 0 for 'never expires'","Skip negative hour computations and send 0 instead","Keep expire_at and expire_hours mutually exclusive in client forms"],"tags":["share","validation","api"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}