{"record":{"id":"c1d4812f78690168","repo":"hibiken/asynq","slug":"rate-newsemaphore-scope-should-not-be-empty","errorCode":null,"errorMessage":"rate.NewSemaphore: scope should not be empty","messagePattern":"rate\\.NewSemaphore: scope should not be empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"x/rate/semaphore.go","lineNumber":27,"sourceCode":"\n\t\"github.com/hibiken/asynq\"\n\tasynqcontext \"github.com/hibiken/asynq/internal/context\"\n\t\"github.com/redis/go-redis/v9\"\n)\n\n// NewSemaphore creates a counting Semaphore for the given scope with the given number of tokens.\nfunc NewSemaphore(rco asynq.RedisConnOpt, scope string, maxTokens int) *Semaphore {\n\trc, ok := rco.MakeRedisClient().(redis.UniversalClient)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"rate.NewSemaphore: unsupported RedisConnOpt type %T\", rco))\n\t}\n\n\tif maxTokens < 1 {\n\t\tpanic(\"rate.NewSemaphore: maxTokens cannot be less than 1\")\n\t}\n\n\tif len(strings.TrimSpace(scope)) == 0 {\n\t\tpanic(\"rate.NewSemaphore: scope should not be empty\")\n\t}\n\n\treturn &Semaphore{\n\t\trc:        rc,\n\t\tscope:     scope,\n\t\tmaxTokens: maxTokens,\n\t}\n}\n\n// Semaphore is a distributed counting semaphore which can be used to set maxTokens across multiple asynq servers.\ntype Semaphore struct {\n\trc        redis.UniversalClient\n\tmaxTokens int\n\tscope     string\n}\n\n// KEYS[1] -> asynq:sema:<scope>\n// ARGV[1] -> max concurrency","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/x/rate/semaphore.go#L9-L45","documentation":"rate.NewSemaphore panics when the scope argument is empty or consists only of whitespace. The scope keys the semaphore's token set in Redis, so an empty scope would produce ambiguous, colliding keys and is rejected up front.","triggerScenarios":"Calling rate.NewSemaphore(rco, \"\", n) or rate.NewSemaphore(rco, \"   \", n) — e.g. when scope is built from a template, tenant ID, or flag that is unset.","commonSituations":"Missing CLI flag or env var for the scope/tenant name; string concatenation producing an empty value; using a variable before it is initialized.","solutions":["Pass a non-empty, meaningful scope string (e.g. \"myapp:api\").","Trim and validate the scope before calling NewSemaphore; return a clear error if blank.","If scope is dynamic, check its source (flag, env var, tenant context) is populated first.","Include an application prefix in the scope to avoid cross-app key collisions in shared Redis."],"exampleFix":"// before\nscope := os.Getenv(\"RATE_SCOPE\") // \"\"\nsem := rate.NewSemaphore(rco, scope, 10)\n// after\nscope := os.Getenv(\"RATE_SCOPE\")\nif strings.TrimSpace(scope) == \"\" { log.Fatal(\"RATE_SCOPE must be set\") }\nsem := rate.NewSemaphore(rco, scope, 10)","handlingStrategy":"validation","validationCode":"func validateScope(scope string) error {\n    if strings.TrimSpace(scope) == \"\" {\n        return errors.New(\"scope must be a non-empty string\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// Go panics are not catchable via try/catch; validate first:\nif err := validateScope(scope); err != nil {\n    return nil, err\n}\nsem := rate.NewSemaphore(rco, scope, tokens)","preventionTips":["Check that flags/env vars feeding the scope are set before constructing.","Use a fixed application-prefixed scope constant where possible.","Trim whitespace on user/tenant-provided scope values.","Add a startup assertion that all semaphore scopes are non-empty."],"tags":["go","panic","validation","rate-limiting","empty-string"],"backgroundTag":"empty-required-field","analyzedSha":"d135f1439bee74e989b7f9b41ecd542cc87f024a","analyzedAt":"2026-09-07T19:02:34.660Z","contentChangedAt":"2026-09-07T19:02:34.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}