Nobody has started this yet — be first.
Business impact
This is arguably the most severe item on this list: it crashes on the routine, expected outcome of rate limiting, not a rare edge case. On the Redis backend (the real multi-instance production deployment), the moment any customer actually reaches their limit -- which is supposed to produce a clean, well-understood 429 -- the request panics instead. That means your best, busiest, most-engaged customers (the ones who hit limits) are exactly the ones who experience this as broken/crashing behavior, likely with noisy error logs or process instability on every occurrence, the opposite of the graceful "please slow down" experience a rate limiter is supposed to provide.
Problem
TokenBucketRedis.AllowN panics instead of returning Allowed: false whenever a request would actually be denied -- which, for a rate limiter, is not a rare edge case, it's a routine and expected outcome that happens on essentially every deployment the moment any client exceeds its limit.
Current behavior
tb := ratelimit.NewTokenBucketRedis(client, 3, 1, "test:tb:"); after exhausting the 3-token capacity, the next call panics: interface conversion: interface {} is nil, not int64.
Expected behavior
The 4th (and every subsequent denied) call should return Result{Allowed: false, ...}, nil -- a normal, recoverable "denied" outcome, exactly like the in-memory implementation and every other denial path in this package.
Steps to reproduce
tb := ratelimit.NewTokenBucketRedis(client, 3, 1, "test:tb:") for i := 0; i < 3; i++ { tb.Allow(ctx, "user-1") } // fine, all 3 allowed tb.Allow(ctx, "user-1") // got: PANIC: interface conversion: interface {} is nil, not int64 // want: Result{Allowed: false, ...}, nil
Why this matters
This is a real, well-known Redis+Lua interop pitfall: Lua's false converts to a RESP nil reply when returned from a script, while Lua's true converts to the RESP integer 1. A Go caller that expects a consistent numeric type in that reply slot (vals[0].(int64)) gets a nil interface{} on the false case and a type assertion panic, not a zero/false value -- meaning the moment your rate limiter needs to say "no," it crashes the request instead.
Suggested approach
Look at exactly how allowed is assigned and returned in pkg/ratelimit/lua/token_bucket.lua, and compare its type against the number 0/1 convention that pkg/ratelimit/lua/leaky_bucket.lua and pkg/ratelimit/lua/sliding_window.lua both still use correctly for the same field, and that the Go side already assumes (vals[0].(int64) == 1). Fix the Lua script's return value, not the Go type assertion.
Acceptance criteria
Verification
go test ./test/ -run TestTask13 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/redis-token-bucket-panicFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/redis-token-bucket-panicSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8080localhost:6380http://localhost:8081curl localhost:8080/healthz
curl localhost:8080/api/v1/rules
curl -s -X POST localhost:8080/api/v1/check -d '{"rule":"free-tier","key":"user-123"}' | jq
No Docker? STORE_BACKEND=memory is the default, so go run ./cmd/server alone (no Redis needed) exercises everything except the Redis-backed code paths.
Work the tickets in tasks/ (ordered easy → hard); each names one Go test in test/:
make test-race # go test -race -count=1 ./...
go test ./test/ -run TestTask01 -v # a single ticket
./test/run_all.sh # all 15, pass/fail summary
Level 1
Fix a bug
Read existing behaviour, correct it.