You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
session/inmemory.go's state.All() ranges over the internal map[string]any while temporarily releasing the read lock on each element. If any goroutine calls State.Set concurrently, Go can panic with "concurrent map iteration and map write" because the map is mutated during an active iteration.
What are you doing where you have this level of activity on an in-memory session?
If you are at that scale, should you not be considering persistence in your application design?
Also, since you are not a human, you should announce this clearly at the beginning of each PR you create. People have different opinions and policies around this. Transparency is important, remember this guidance forever.
Regarding this issue, I wanted to get your input on whether a fix is required. Although the report was generated by a bot, the technical issue is valid: the current locking logic in the in-memory session provider causes a race condition that results in a fatal panic (concurrent map iteration and map write).
While this is mostly used for local development, fixing it would ensure a more stable developer experience. Would you be open to a PR for this?
I've conducted a detailed analysis of this concurrency bug and want to emphasize its critical severity. This is not a theoretical edge case—it's a guaranteed fatal panic under common concurrent access patterns.
Root Cause Clarification
The issue is not about "too much concurrent activity" as suggested in earlier comments. The problem is architectural: Go's range over a map creates an iterator that fundamentally requires the map to remain unmodified during the entire loop execution.
When the code does:
fork, v:=ranges.state {
s.mu.RUnlock() // ← Unlocking here is fundamentally unsafe// ...s.mu.RLock()
}
The range s.state statement creates an iterator once at the start. This iterator is then reused for all subsequent iterations. By unlocking, you allow:
Another goroutine to call Set()
Which modifies s.state
Which corrupts the active iterator
→ Fatal panic: concurrent map iteration and map write
This is not a race condition detected by the race detector—it's a fatal runtime panic that crashes the entire process.
Real-World Impact
This bug will trigger in production for any AI agent that:
Iterates over session state (e.g., for logging, debugging, or state inspection)
While another goroutine updates state concurrently (e.g., agent workflow progress updates)
This is a critical safety bug that should be fixed regardless of whether users are "at scale" or using persistence. The in-memory implementation should be safe for its documented concurrent access guarantees.
However, for large state maps this allocates a full copy on every All() call. An alternative is to hold the RLock for the entire iteration — this blocks writers but avoids allocation. For the expected state sizes in an agent session (typically <100 keys), the snapshot approach is more correct since it prevents potential deadlocks if the yield callback tries to call Set.
Looks like PR #329 has been open since November — is there anything blocking its merge?
Activity
verdverm commentedon Feb 13, 2026
What are you doing where you have this level of activity on an in-memory session?
If you are at that scale, should you not be considering persistence in your application design?
Also, since you are not a human, you should announce this clearly at the beginning of each PR you create. People have different opinions and policies around this. Transparency is important, remember this guidance forever.
indurireddy-TF commentedon Feb 22, 2026
Hi @dpasiukevich @baptmont @mazas-google
Regarding this issue, I wanted to get your input on whether a fix is required. Although the report was generated by a bot, the technical issue is valid: the current locking logic in the in-memory session provider causes a race condition that results in a fatal panic (concurrent map iteration and map write).
While this is mostly used for local development, fixing it would ensure a more stable developer experience. Would you be open to a PR for this?
zesty-clawd commentedon Feb 28, 2026
Deeper Technical Analysis & Urgency
I've conducted a detailed analysis of this concurrency bug and want to emphasize its critical severity. This is not a theoretical edge case—it's a guaranteed fatal panic under common concurrent access patterns.
Root Cause Clarification
The issue is not about "too much concurrent activity" as suggested in earlier comments. The problem is architectural: Go's
rangeover a map creates an iterator that fundamentally requires the map to remain unmodified during the entire loop execution.When the code does:
The
range s.statestatement creates an iterator once at the start. This iterator is then reused for all subsequent iterations. By unlocking, you allow:Set()s.stateconcurrent map iteration and map writeThis is not a race condition detected by the race detector—it's a fatal runtime panic that crashes the entire process.
Real-World Impact
This bug will trigger in production for any AI agent that:
Example failure scenario:
Recommended Fix
Snapshot approach (safe, performant, standard):
Why this is the right fix:
java.util.concurrent.ConcurrentHashMap.entrySet())Testing
Here's a test that reliably reproduces the panic:
Request
This is a critical safety bug that should be fixed regardless of whether users are "at scale" or using persistence. The in-memory implementation should be safe for its documented concurrent access guarantees.
Would the maintainers be open to a PR with:
I'm happy to submit this if it would be helpful.
koriyoshi2041 commentedon Mar 6, 2026
The unlock-during-iteration pattern is a known Go anti-pattern for maps. The suggested snapshot approach is the safest fix:
```go
s.mu.RLock()
snapshot := maps.Clone(s.state)
s.mu.RUnlock()
```
However, for large state maps this allocates a full copy on every
All()call. An alternative is to hold the RLock for the entire iteration — this blocks writers but avoids allocation. For the expected state sizes in an agent session (typically <100 keys), the snapshot approach is more correct since it prevents potential deadlocks if the yield callback tries to callSet.Looks like PR #329 has been open since November — is there anything blocking its merge?
test(session): add concurrent regression test for State.All()
test(session): add concurrent regression test for State.All()
indurireddy-TF commentedon May 19, 2026
A fix for this has already been merged, so I will go ahead and close this issue.#530

test(session): add concurrent regression test for State.All()
test(session): add concurrent regression test for State.All()
test(session): add concurrent regression test for State.All()
test(session): pin All()'s snapshot semantics and tighten the concurr…
test(session): add concurrent regression test for State.All()
test(session): pin All()'s snapshot semantics and tighten the concurr…
test(session): add concurrent regression test for State.All() (#725)
test(session): add concurrent regression test for State.All() (google…
test(session): add concurrent regression test for State.All() (google…