Summary
Session scalar fields are written cross-goroutine bypassing Session.mu, while sibling accessors guard the exact same fields.
Locations
pkg/server/session_manager.go:964 rt.session.Title = title (HTTP goroutine) vs pkg/runtime/runtime.go:1399 and session_manager.go:998 (generateTitle goroutine) — no locked SetTitle exists.
pkg/session/store.go:438-441 UpdateSessionTokens writes InputTokens/OutputTokens/Cost directly, while SetUsage/Usage (session.go:595-610) guard those with s.mu; store.go:453 same for Title.
Fix
Add locked SetTitle / SetTokensAndCost methods on Session; route all writers through them.
Acceptance
- All Title/token/cost writes go through locked setters;
-race clean; regression coverage.
Summary
Session scalar fields are written cross-goroutine bypassing
Session.mu, while sibling accessors guard the exact same fields.Locations
pkg/server/session_manager.go:964rt.session.Title = title(HTTP goroutine) vspkg/runtime/runtime.go:1399and session_manager.go:998 (generateTitle goroutine) — no lockedSetTitleexists.pkg/session/store.go:438-441UpdateSessionTokenswritesInputTokens/OutputTokens/Costdirectly, whileSetUsage/Usage(session.go:595-610) guard those withs.mu; store.go:453 same for Title.Fix
Add locked
SetTitle/SetTokensAndCostmethods on Session; route all writers through them.Acceptance
-raceclean; regression coverage.