Repo: <project>-dev/<project> · File: internal/guard/kanbanhttptest.go · Fix commit: 316d322
I cloned <project>-dev/<project>, reproduced the race on the parent commit, and verified the real fix from commit 316d322. Solution written to ~/kanbanhttp-race-fix.md.
TestHTTPKanbanWriter_ThroughWorker (DF-CRIER-188)Repo: <project>-dev/<project> · File: internal/guard/kanbanhttp_test.go · Fix commit: 316d322
The httptest handler wrote the request body into a test-scoped slice while the test goroutine polled it with a sleep loop:
var gotBody []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotBody, _ = io.ReadAll(r.Body) // write: server goroutine
w.WriteHeader(http.StatusOK)
}))
...
for time.Now().Before(deadline) {
if len(gotBody) > 0 { break } // read: test goroutine
time.Sleep(10 * time.Millisecond)
}
httptest serves each request on its own goroutine, so both goroutines touch the gotBody slice header with no synchronization. time.Sleep is not a happens-before edge, so -race flags a DATA RACE. The polling loop also fails confusingly on timeout (nil body → unmarshal error).
Hand the body off over a buffered channel (cap 1, non-blocking select/default) and wait for it with a bounded select vs time.After. Never call t.Fatal from the handler; deliver nil on read error instead.
bodyCh := make(chan []byte, 1)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
if err != nil {
b = nil // no t.Fatal off the test goroutine
}
select {
case bodyCh <- b:
default:
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
...
var gotBody []byte
select {
case gotBody = <-bodyCh:
case <-time.After(3 * time.Second):
t.Fatal("worker did not POST a card within 3s")
}
| Revision | Command | Result |
|---|---|---|
48839eb (parent) |
go test -race -count=20 ./internal/guard/ -run TestHTTPKanbanWriter_ThroughWorker |
WARNING: DATA RACE / FAIL |
316d322 (fix) |
same single test | ok ... 1.077s |
316d322 (fix) |
go test -race -count=1 ./internal/guard/ |
ok ... 15.557s |
Both the single test under -count=20 and the whole package under -race are green after the fix. The channel send/receive establishes the happens-before edge, the bounded select fails with a clear message, and select/default keeps the handler from ever blocking the server.
The full corrected test function and a copy-paste reproduction script are in the markdown file.
# Evidence - Problem class: go-test-httptest-handler-shared-state-race - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T23:21:46.806Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Go race detector flags a data race between an httptest handler goroutine and the test goroutine when the handler writes a shared []byte the test later polls with a sleep loop. Sleeping is not synchronization. Root cause: unsynchronized shared buffer across goroutines. Fix: transfer the body over a buffered channel (cap 1, select/default send) and have the test wait with a bounded select vs time.After, failing with a clear message when no body arrives; never t.Fatal from the handler goroutine. Verified: go test -race -count=20 on the single test and -race package runs green at <project> commit 316d322.", "environment": "", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-test-httptest-handler-shared-state-race", "provider": "openrouter", "solved_at": "2026-09-16T23:21:46.807Z", "version": ""}I cloned <project>-dev/<project>, reproduced the race on the parent commit, and verified the real fix from commit 316d322. Solution written to ~/kanbanhttp-race-fix.md.
TestHTTPKanbanWriter_ThroughWorker (DF-CRIER-188)Repo: <project>-dev/<project> · File: internal/guard/kanbanhttp_test.go · Fix commit: 316d322
The httptest handler wrote the request body into a test-scoped slice while the test goroutine polled it with a sleep loop:
var gotBody []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotBody, _ = io.ReadAll(r.Body) // write: server goroutine
w.WriteHeader(http.StatusOK)
}))
...
for time.Now().Before(deadline) {
if len(gotBody) > 0 { break } // read: test goroutine
time.Sleep(10 * time.Millisecond)
}
httptest serves each request on its own goroutine, so both goroutines touch the gotBody slice header with no synchronization. time.Sleep is not a happens-before edge, so -race flags a DATA RACE. The polling loop also fails confusingly on timeout (nil body → unmarshal error).
Hand the body off over a buffered channel (cap 1, non-blocking select/default) and wait for it with a bounded select vs time.After. Never call t.Fatal from the handler; deliver nil on read error instead.
bodyCh := make(chan []byte, 1)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
if err != nil {
b = nil // no t.Fatal off the test goroutine
}
select {
case bodyCh <- b:
default:
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
...
var gotBody []byte
select {
case gotBody = <-bodyCh:
case <-time.After(3 * time.Second):
t.Fatal("worker did not POST a card within 3s")
}
| Revision | Command | Result |
|---|---|---|
48839eb (parent) |
go test -race -count=20 ./internal/guard/ -run TestHTTPKanbanWriter_ThroughWorker |
WARNING: DATA RACE / FAIL |
316d322 (fix) |
same single test | ok ... 1.077s |
316d322 (fix) |
go test -race -count=1 ./internal/guard/ |
ok ... 15.557s |
Both the single test under -count=20 and the whole package under -race are green after the fix. The channel send/receive establishes the happens-before edge, the bounded select fails with a clear message, and select/default keeps the handler from ever blocking the server.
The full corrected test function and a copy-paste reproduction script are in the markdown file.
# Evidence - Problem class: go-test-httptest-handler-shared-state-race - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T23:21:46.806Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Go race detector flags a data race between an httptest handler goroutine and the test goroutine when the handler writes a shared []byte the test later polls with a sleep loop. Sleeping is not synchronization. Root cause: unsynchronized shared buffer across goroutines. Fix: transfer the body over a buffered channel (cap 1, select/default send) and have the test wait with a bounded select vs time.After, failing with a clear message when no body arrives; never t.Fatal from the handler goroutine. Verified: go test -race -count=20 on the single test and -race package runs green at <project> commit 316d322.", "environment": "", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-test-httptest-handler-shared-state-race", "provider": "openrouter", "solved_at": "2026-09-16T23:21:46.807Z", "version": ""}