#4664: fix: per-session metadata files to eliminate lock contention
Cluster:
Session Lock Improvements
## Summary
Instead of all sessions sharing a single `sessions.json` with a global file lock, each session now gets its own `.meta.json` file for frequent updates. This eliminates lock contention when multiple sessions are active simultaneously.
## Problem
When chatting on multiple channels (e.g., Slack and Telegram) simultaneously, sessions would block each other because `updateSessionStoreEntry` required an exclusive lock on the shared `sessions.json` file.
## Solution
- New `per-session-store.ts` with atomic write functions for per-session `.meta.json` files
- `updateSessionStoreEntry` now writes to per-session files without acquiring the global lock
- Background debounced sync keeps `sessions.json` in sync (best-effort, 5s debounce)
- Fallback to locked update for legacy entries without sessionId
## Testing
- [x] Tested locally with concurrent Slack and Telegram sessions
- [x] Linter passes
## AI Assistance 🤖
This PR was AI-assisted (Claude). The fix was identified by tracing the session update flow and finding the global lock bottleneck reported in #3092.
Fixes #3092
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR reduces contention on `sessions.json` by introducing per-session `.meta.json` files and updating `updateSessionStoreEntry` to write frequent updates to those per-session files without taking the global session-store lock. A debounced background task periodically syncs the updated entries back into `sessions.json` under the existing lock, and legacy entries without `sessionId` continue to use the locked update path.
Overall this aligns with the repository’s existing atomic-write approach for `sessions.json`, but the new flow introduces some correctness/portability edge cases (agentId path parsing, and concurrent updates to the same session) that are worth addressing before relying on it broadly.
<h3>Confidence Score: 3/5</h3>
- This PR is reasonably safe to merge but has a couple of correctness/portability edge cases that could surface under concurrency or non-Windows paths.
- The overall design (per-session files + best-effort sync) is sound and localized, but `extractAgentIdFromStorePath` appears path-separator dependent, and the per-session update path can lose updates if multiple writers touch the same session concurrently because it’s a read-merge-write without per-session serialization. The rest are minor robustness concerns (file permissions parity, unbounded pending syncs).
- src/config/sessions/store.ts, src/config/sessions/per-session-store.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#15882: fix: move session entry computation inside store lock to prevent ra...
by cloorus · 2026-02-14
84.3%
#16061: fix(sessions): tolerate invalid sessionFile metadata
by haoyifan · 2026-02-14
83.8%
#16542: fix(sessions): use atomic temp+rename write on Windows
by aldoeliacim · 2026-02-14
81.3%
#6653: fix: persist archived session entry on /new or /reset
by leicao-me · 2026-02-01
80.3%
#21828: fix: acquire session write lock in delivery mirror and gateway chat...
by inkolin · 2026-02-20
80.3%
#19528: feat: directory-per-session store to eliminate monolithic JSON bott...
by binary64 · 2026-02-17
80.3%
#17132: fix: filter out invalid session entries with empty sessionFile
by Limitless2023 · 2026-02-15
80.0%
#20336: fix(sessions): resolve transcriptPath using agentId when storePath ...
by Limitless2023 · 2026-02-18
79.9%
#20431: fix(sessions): add session contamination guards and self-leak lock ...
by marcomarandiz · 2026-02-18
79.1%
#4044: fix: release session locks on SIGUSR1 restart + instance nonce for ...
by seanb4t · 2026-01-29
79.1%