hibiken/asynq · error
rate.NewSemaphore: scope should not be empty
Error message
rate.NewSemaphore: scope should not be empty
What it means
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.
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.
Example fix
// before
scope := os.Getenv("RATE_SCOPE") // ""
sem := rate.NewSemaphore(rco, scope, 10)
// after
scope := os.Getenv("RATE_SCOPE")
if strings.TrimSpace(scope) == "" { log.Fatal("RATE_SCOPE must be set") }
sem := rate.NewSemaphore(rco, scope, 10) Defensive patterns
Strategy: validation
Validate before calling
func validateScope(scope string) error {
if strings.TrimSpace(scope) == "" {
return errors.New("scope must be a non-empty string")
}
return nil
} Try / catch
// Go panics are not catchable via try/catch; validate first:
if err := validateScope(scope); err != nil {
return nil, err
}
sem := rate.NewSemaphore(rco, scope, tokens) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Missing CLI flag or env var for the scope/tenant name; string concatenation producing an empty value; using a variable before it is initialized.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- rate.NewSemaphore: maxTokens cannot be less than 1
- asynq: invalid pattern
- rate.NewSemaphore: unsupported RedisConnOpt type %T
- provided context must have a deadline
- provided context is missing task ID value
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/c1d4812f78690168.
Report an issue: GitHub.
Appendix: source
Thrown at x/rate/semaphore.go:27
"github.com/hibiken/asynq"
asynqcontext "github.com/hibiken/asynq/internal/context"
"github.com/redis/go-redis/v9"
)
// NewSemaphore creates a counting Semaphore for the given scope with the given number of tokens.
func NewSemaphore(rco asynq.RedisConnOpt, scope string, maxTokens int) *Semaphore {
rc, ok := rco.MakeRedisClient().(redis.UniversalClient)
if !ok {
panic(fmt.Sprintf("rate.NewSemaphore: unsupported RedisConnOpt type %T", rco))
}
if maxTokens < 1 {
panic("rate.NewSemaphore: maxTokens cannot be less than 1")
}
if len(strings.TrimSpace(scope)) == 0 {
panic("rate.NewSemaphore: scope should not be empty")
}
return &Semaphore{
rc: rc,
scope: scope,
maxTokens: maxTokens,
}
}
// Semaphore is a distributed counting semaphore which can be used to set maxTokens across multiple asynq servers.
type Semaphore struct {
rc redis.UniversalClient
maxTokens int
scope string
}
// KEYS[1] -> asynq:sema:<scope>
// ARGV[1] -> max concurrencyView on GitHub (pinned to d135f1439b)