Summary
Systematic analysis of all 135 _test.go files identified 22 distinct flakiness patterns that can cause intermittent CI failures, test hangs, and fatal panics.
Breakdown: 9 CRITICAL · 8 HIGH · 5 MEDIUM
CRITICAL — Likely Causing Failures Today
1. Concurrent map access without mutex — originator_stream_test.go
Lines: pkg/sync/originator_stream_test.go:162, 197, 282, 341, 378, 433, 504
lastSequenceIds maps are written by goroutines and read in require.Eventually polling loops without synchronization. Go's runtime will panic:
fatal error: concurrent map read and map write
Fix: Wrap with sync.RWMutex or use sync.Map.
2. Channel reads without timeout — can hang forever
pkg/registry/notifier_test.go:62-76 — for v := range ch blocks forever if channel never closes
pkg/indexer/common/log_handler_test.go:99-107 — unbuffered done channel; goroutine orphaned on timeout, may panic on double-close
pkg/sync/originator_stream_test.go — direct <-ch reads without select/timeout
Fix: Always use select with case <-time.After(...) or test-scoped context deadline.
3. Package-level state mutation without synchronization
pkg/tracing/tracing_test.go:13-19 — apmEnabled (package-level bool) set/restored via t.Cleanup() with no mutex. Parallel tests race on this value.
pkg/api/message/subscribe_test.go:33-50 — allRows (package-level slice) re-assigned in setupTest(). Parallel tests overwrite each other's data.
Fix: Protect with sync.Mutex or restructure so each test owns its own data.
4. Ultra-tight require.Eventually timeouts (500ms for DB ops)
pkg/indexer/storer/groupMessage_test.go — 50ms tick / 500ms timeout
pkg/indexer/storer/identityUpdate_test.go — 50ms tick / 500ms timeout
A single slow query or GC pause under CI load causes failure.
Fix: Increase to 5s+ timeout.
5. Goroutines spawned without lifecycle management
pkg/sync/originator_stream_test.go:158-160, 193-195, 276, 333, 376, 431, 500, 572, 634
go dbStorerInstance.Start() called without WaitGroup, done channel, or t.Cleanup. Tests exit before goroutine finishes → resource leaks, stale goroutines writing to closed DB connections.
Fix: Use t.Cleanup() to cancel context and wait for goroutine completion.
6. Unfired barrier channel — goroutine leak
pkg/blockchain/noncemanager/manager_test.go:264-300
Barrier channel created but never closed. All 50 goroutines block forever on <-barrier.
Fix: Add close(barrier) to release goroutines.
HIGH — Frequent Intermittent Failures
7. time.Sleep as synchronization (~50+ instances)
Most problematic:
| File |
Sleep |
Waiting For |
pkg/payerreport/store_test.go:185 |
1ms |
DB write |
pkg/payerreport/workers/settlement_test.go:516 |
1ms |
Async op |
pkg/blockchain/noncemanager/redis/manager_test.go:52,182 |
1ms |
Redis ops |
pkg/blockchain/noncemanager/manager_test.go:303 |
1ms |
Concurrent nonce detection |
pkg/db/sequences_test.go:58,78 |
10ms |
DB locking |
pkg/db/payer_test.go:68 |
5ms |
Concurrent payer creation |
pkg/tracing/tracing_test.go:82,107 |
100ms |
50ms TTL expiry (only 2x margin) |
pkg/blockchain/oracle/oracle_test.go:101 |
300ms |
250ms stale threshold (50ms margin) |
Fix: Replace with require.Eventually or event-driven synchronization.
8. Sleeps coupled to internal polling constants
| File |
Pattern |
pkg/api/message/subscribe_topics_test.go:94 |
Sleep(SubscribeWorkerPollTime + 100ms) |
pkg/api/metadata/cursor_test.go:99 |
Sleep(SubscribeWorkerPollTime + 100ms) |
pkg/api/message/subscribe_test.go:620 |
Sleep(4 * SubscribeWorkerPollTime) |
Fix: Use require.Eventually instead of coupling to implementation internals.
9. reflect.DeepEqual on proto messages
pkg/server/server_test.go:178-180, 200-202 — proto structs have unexported internal fields that cause spurious inequality. Note: pkg/api/message/publish_test.go:84 correctly uses proto.Equal().
Fix: Replace with proto.Equal().
10. Unsynchronized counter in flaky query helper
pkg/db/subscription_test.go:115-125 — numQueries++ in a closure without synchronization creates a data race when called concurrently by the polling mechanism.
Fix: Use atomic.Int32.
11. Random sleep in test — non-reproducible
pkg/blockchain/oracle/oracle_test.go:84 — time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond). Failures cannot be reproduced.
Fix: Use fixed sleep or event-driven sync.
12. Unseeded rand in test inputs
pkg/api/message/subscribe_test.go:391 — rand.Intn(high - low)
pkg/db/gateway_envelope_batch_test.go:59,152 — rand.Intn(10) + 1 for batch size
Different behavior every run; failures non-reproducible.
Fix: Use fixed seed or deterministic inputs.
13. Port reuse race
pkg/db/pgx_test.go:150-176 — listener opened on :0, port captured, listener closed, then port reused. Between close and rebind another process can claim the port.
Fix: Pass the listener directly instead of close-and-rebind.
14. gRPC listener leak
pkg/api/payer/publish_test.go:264-283 — net.Listen + grpc.NewServer without t.Cleanup(s.GracefulStop). Listener leaks if test fails before goroutine starts.
Fix: Add t.Cleanup(func() { s.GracefulStop() }).
MEDIUM — Occasional Failures
15. context.Background() instead of test-scoped context
Widespread (~20+ files), especially pkg/prune/prune_test.go (13 occurrences). Test timeouts don't propagate; hung operations hang forever.
Fix: Use t.Context() (Go 1.24+).
16. Missing t.Cleanup for servers/workers
pkg/blockchain/rpcMultiClient_test.go — HTTP test servers
pkg/sync/syncworker_test.go — workers
pkg/api/server_test.go — server shutdown
17. Database result ordering assumptions
pkg/prune/prune_test.go:194,240 — SELECT without ORDER BY
pkg/db/gateway_envelope_batch_test.go:362 — payer_id query without ordering
pkg/api/message/subscribe_test.go — notification order assumed deterministic
Fix: Add ORDER BY or sort results before comparison.
18. Tight time.After deadlines in select blocks
| File |
Timeout |
What It Guards |
pkg/indexer/common/log_handler_test.go:105,166 |
1s |
Async DB work |
pkg/api/message/publish_worker_test.go:408 |
2s |
Worker exit |
Fix: Use 10s+ for I/O-bound operations in CI.
19. Hardcoded Redis address shared across all tests
pkg/testutils/redis/redis.go:14 — localhost:6379 DB 15. All Redis tests compete for the same instance.
Fix: Use testcontainers or verify availability before tests.
20. Concurrent Anvil container startup race
pkg/testutils/anvil/anvil_test.go:26-49 — 10 concurrent containers with 5s timeout and no backoff.
21. Connection pool exhaustion under parallel load
pkg/testutils/store.go:70-74 — default pool (25 conns) shared across parallel tests.
22. Transaction lock race in prune tests
pkg/prune/prune_test.go:203-235 — test holds lock and runs executor with no guaranteed timeout behavior.
Top 10 Flakiest Files
| Rank |
File |
Issues |
| 1 |
pkg/sync/originator_stream_test.go |
Concurrent map access, orphaned goroutines, no lifecycle mgmt |
| 2 |
pkg/api/message/subscribe_test.go |
Shared global state, ordering assumptions, unseeded rand |
| 3 |
pkg/tracing/tracing_test.go |
Package-level state race, tight TTL margins |
| 4 |
pkg/indexer/common/log_handler_test.go |
1s timeout, unbuffered done channel, goroutine leak |
| 5 |
pkg/registry/notifier_test.go |
Unbounded channel read, 100 unsynced goroutines |
| 6 |
pkg/server/server_test.go |
reflect.DeepEqual on protos, lifecycle races |
| 7 |
pkg/blockchain/noncemanager/manager_test.go |
Unfired barrier, 1ms sleeps |
| 8 |
pkg/blockchain/oracle/oracle_test.go |
Random sleep, 50ms timing margin |
| 9 |
pkg/db/subscription_test.go |
Unsynchronized counter, context lifecycle |
| 10 |
pkg/db/gateway_envelope_batch_test.go |
Unseeded rand, missing ORDER BY |
Suggested Fix Priority
Immediate (prevents panics and hangs):
- Mutex on
lastSequenceIds map in originator_stream_test.go
- Timeouts on all bare channel reads
close(barrier) in nonce manager test
- Mutex on
apmEnabled in tracing tests
atomic.Int32 for numQueries in subscription_test.go
proto.Equal instead of reflect.DeepEqual in server_test.go
Short-term (reduces CI flakiness):
7. Replace all 1ms sleeps with require.Eventually
8. Increase 500ms Eventually timeouts to 5s+
9. t.Cleanup for all goroutines/servers started in tests
10. Fix port reuse race in pgx_test.go
Medium-term (improves reliability):
11. Migrate context.Background() → t.Context() across test suite
12. Replace proto require.Equal with protocmp.Transform()
13. Remove sleep coupling to SubscribeWorkerPollTime
14. Sort results before slice comparisons in query tests
15. Fixed seed or deterministic inputs for rand in tests
🤖 Generated with Claude Code
Summary
Systematic analysis of all 135
_test.gofiles identified 22 distinct flakiness patterns that can cause intermittent CI failures, test hangs, and fatal panics.Breakdown: 9 CRITICAL · 8 HIGH · 5 MEDIUM
CRITICAL — Likely Causing Failures Today
1. Concurrent map access without mutex —
originator_stream_test.goLines:
pkg/sync/originator_stream_test.go:162, 197, 282, 341, 378, 433, 504lastSequenceIdsmaps are written by goroutines and read inrequire.Eventuallypolling loops without synchronization. Go's runtime will panic:Fix: Wrap with
sync.RWMutexor usesync.Map.2. Channel reads without timeout — can hang forever
pkg/registry/notifier_test.go:62-76—for v := range chblocks forever if channel never closespkg/indexer/common/log_handler_test.go:99-107— unbuffereddonechannel; goroutine orphaned on timeout, may panic on double-closepkg/sync/originator_stream_test.go— direct<-chreads withoutselect/timeoutFix: Always use
selectwithcase <-time.After(...)or test-scoped context deadline.3. Package-level state mutation without synchronization
pkg/tracing/tracing_test.go:13-19—apmEnabled(package-level bool) set/restored viat.Cleanup()with no mutex. Parallel tests race on this value.pkg/api/message/subscribe_test.go:33-50—allRows(package-level slice) re-assigned insetupTest(). Parallel tests overwrite each other's data.Fix: Protect with
sync.Mutexor restructure so each test owns its own data.4. Ultra-tight
require.Eventuallytimeouts (500ms for DB ops)pkg/indexer/storer/groupMessage_test.go— 50ms tick / 500ms timeoutpkg/indexer/storer/identityUpdate_test.go— 50ms tick / 500ms timeoutA single slow query or GC pause under CI load causes failure.
Fix: Increase to 5s+ timeout.
5. Goroutines spawned without lifecycle management
pkg/sync/originator_stream_test.go:158-160, 193-195, 276, 333, 376, 431, 500, 572, 634go dbStorerInstance.Start()called without WaitGroup, done channel, ort.Cleanup. Tests exit before goroutine finishes → resource leaks, stale goroutines writing to closed DB connections.Fix: Use
t.Cleanup()to cancel context and wait for goroutine completion.6. Unfired barrier channel — goroutine leak
pkg/blockchain/noncemanager/manager_test.go:264-300Barrier channel created but never closed. All 50 goroutines block forever on
<-barrier.Fix: Add
close(barrier)to release goroutines.HIGH — Frequent Intermittent Failures
7.
time.Sleepas synchronization (~50+ instances)Most problematic:
pkg/payerreport/store_test.go:185pkg/payerreport/workers/settlement_test.go:516pkg/blockchain/noncemanager/redis/manager_test.go:52,182pkg/blockchain/noncemanager/manager_test.go:303pkg/db/sequences_test.go:58,78pkg/db/payer_test.go:68pkg/tracing/tracing_test.go:82,107pkg/blockchain/oracle/oracle_test.go:101Fix: Replace with
require.Eventuallyor event-driven synchronization.8. Sleeps coupled to internal polling constants
pkg/api/message/subscribe_topics_test.go:94Sleep(SubscribeWorkerPollTime + 100ms)pkg/api/metadata/cursor_test.go:99Sleep(SubscribeWorkerPollTime + 100ms)pkg/api/message/subscribe_test.go:620Sleep(4 * SubscribeWorkerPollTime)Fix: Use
require.Eventuallyinstead of coupling to implementation internals.9.
reflect.DeepEqualon proto messagespkg/server/server_test.go:178-180, 200-202— proto structs have unexported internal fields that cause spurious inequality. Note:pkg/api/message/publish_test.go:84correctly usesproto.Equal().Fix: Replace with
proto.Equal().10. Unsynchronized counter in flaky query helper
pkg/db/subscription_test.go:115-125—numQueries++in a closure without synchronization creates a data race when called concurrently by the polling mechanism.Fix: Use
atomic.Int32.11. Random sleep in test — non-reproducible
pkg/blockchain/oracle/oracle_test.go:84—time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond). Failures cannot be reproduced.Fix: Use fixed sleep or event-driven sync.
12. Unseeded
randin test inputspkg/api/message/subscribe_test.go:391—rand.Intn(high - low)pkg/db/gateway_envelope_batch_test.go:59,152—rand.Intn(10) + 1for batch sizeDifferent behavior every run; failures non-reproducible.
Fix: Use fixed seed or deterministic inputs.
13. Port reuse race
pkg/db/pgx_test.go:150-176— listener opened on:0, port captured, listener closed, then port reused. Between close and rebind another process can claim the port.Fix: Pass the listener directly instead of close-and-rebind.
14. gRPC listener leak
pkg/api/payer/publish_test.go:264-283—net.Listen+grpc.NewServerwithoutt.Cleanup(s.GracefulStop). Listener leaks if test fails before goroutine starts.Fix: Add
t.Cleanup(func() { s.GracefulStop() }).MEDIUM — Occasional Failures
15.
context.Background()instead of test-scoped contextWidespread (~20+ files), especially
pkg/prune/prune_test.go(13 occurrences). Test timeouts don't propagate; hung operations hang forever.Fix: Use
t.Context()(Go 1.24+).16. Missing
t.Cleanupfor servers/workerspkg/blockchain/rpcMultiClient_test.go— HTTP test serverspkg/sync/syncworker_test.go— workerspkg/api/server_test.go— server shutdown17. Database result ordering assumptions
pkg/prune/prune_test.go:194,240— SELECT without ORDER BYpkg/db/gateway_envelope_batch_test.go:362— payer_id query without orderingpkg/api/message/subscribe_test.go— notification order assumed deterministicFix: Add ORDER BY or sort results before comparison.
18. Tight
time.Afterdeadlines in select blockspkg/indexer/common/log_handler_test.go:105,166pkg/api/message/publish_worker_test.go:408Fix: Use 10s+ for I/O-bound operations in CI.
19. Hardcoded Redis address shared across all tests
pkg/testutils/redis/redis.go:14—localhost:6379DB 15. All Redis tests compete for the same instance.Fix: Use testcontainers or verify availability before tests.
20. Concurrent Anvil container startup race
pkg/testutils/anvil/anvil_test.go:26-49— 10 concurrent containers with 5s timeout and no backoff.21. Connection pool exhaustion under parallel load
pkg/testutils/store.go:70-74— default pool (25 conns) shared across parallel tests.22. Transaction lock race in prune tests
pkg/prune/prune_test.go:203-235— test holds lock and runs executor with no guaranteed timeout behavior.Top 10 Flakiest Files
pkg/sync/originator_stream_test.gopkg/api/message/subscribe_test.gopkg/tracing/tracing_test.gopkg/indexer/common/log_handler_test.gopkg/registry/notifier_test.gopkg/server/server_test.goreflect.DeepEqualon protos, lifecycle racespkg/blockchain/noncemanager/manager_test.gopkg/blockchain/oracle/oracle_test.gopkg/db/subscription_test.gopkg/db/gateway_envelope_batch_test.goSuggested Fix Priority
Immediate (prevents panics and hangs):
lastSequenceIdsmap inoriginator_stream_test.goclose(barrier)in nonce manager testapmEnabledin tracing testsatomic.Int32fornumQueriesinsubscription_test.goproto.Equalinstead ofreflect.DeepEqualinserver_test.goShort-term (reduces CI flakiness):
7. Replace all 1ms sleeps with
require.Eventually8. Increase 500ms Eventually timeouts to 5s+
9.
t.Cleanupfor all goroutines/servers started in tests10. Fix port reuse race in
pgx_test.goMedium-term (improves reliability):
11. Migrate
context.Background()→t.Context()across test suite12. Replace proto
require.Equalwithprotocmp.Transform()13. Remove sleep coupling to
SubscribeWorkerPollTime14. Sort results before slice comparisons in query tests
15. Fixed seed or deterministic inputs for
randin tests🤖 Generated with Claude Code