Nobody has started this yet — be first.
Business impact
This breaks the Redis backend's core promise -- the exact thing pkg/idempotency's README section on concurrency correctness spends the most words defending -- for every single request, not just an edge case. Any deployment running STORE_BACKEND=redis (the recommended, multi-replica-safe production configuration) would see every retried request rejected with 409 idempotency_key_reused, even though the retry is byte-for-byte identical to the original. A client that retries after a timeout (the single most common reason a client resends a request with the same Idempotency-Key) gets a hard, unrecoverable conflict instead of the replayed response it's supposed to get -- indistinguishable, from the client's point of view, from a real bug in their own request construction, which is exactly the kind of misleading error that burns hours of the wrong team's time during an incident.
Problem
RedisStore.Begin calls a Lua script (begin.lua) that expects its arguments in a specific order: ARGV[1] = fingerprint, ARGV[2] = ttl_ms, ARGV[3] = created_at. The Go call site passes them in a different order.
Current behavior
The Redis hash's fingerprint field ends up storing a timestamp string instead of the actual fingerprint hash. Since a fresh time.Now() timestamp is computed on every Begin call, two calls with the same real fingerprint compare two different stored "fingerprint" values (both of which are actually timestamps) and are therefore always judged to differ -- every second-and-later Begin call for a given key returns Conflict, regardless of whether the actual request fingerprint matches.
Expected behavior
A second Begin call with a fingerprint matching the first should return Replay (if the first completed/failed) or InProgress (if the first is still pending) -- never Conflict.
Steps to reproduce
store := idempotency.NewRedisStore(client, time.Second) store.Begin(ctx, "k", "same-fingerprint", time.Minute) store.Complete(ctx, "k", idempotency.StoredResponse{StatusCode: 200}) time.Sleep(2 * time.Millisecond) // ensure a different wall-clock timestamp claim, _ := store.Begin(ctx, "k", "same-fingerprint", time.Minute) fmt.Println(claim.Status) // got: Conflict, want: Replay
Why this matters
This is a genuine cross-component protocol mismatch: begin.lua and its Go caller each independently encode an assumption about what each positional ARGV slot means, and nothing in the type system (Lua has no static types, and redis.Script.Run takes a bare ...interface{}) can catch the two sides drifting apart. No Redis-level error is raised either -- both a timestamp string and a fingerprint hash are just opaque strings to Redis and to Lua, so the corruption is entirely silent until you notice the behavioral symptom (spurious conflicts), which is exactly what makes this the hardest bug in this task list to track down from symptoms alone.
Suggested approach
Read pkg/idempotency/lua/begin.lua's comment block at the top -- it documents exactly which ARGV index means what. Then look at the actual argument list in the beginScript.Run(...) call inside RedisStore.Begin (pkg/idempotency/redis.go) and check whether the order of the three trailing arguments actually matches that documented contract.
Acceptance criteria
Verification
go test ./test/ -run TestTask14 -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-lua-argv-swapFix 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-lua-argv-swapSubmit 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:8080POST /v1/chargesTo run against Redis (the multi-replica-safe backend) with zero local setup:
docker compose up --build # app + redis, hot-reloading on save
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
Level 1
Fix a bug
Read existing behaviour, correct it.