Nanobot
Nanobot serializes session files before recovery lies
Nanobot proposes serializing canonical session-file access, retrying atomic replacement on transient Windows permission failures and restoring in-memory state when archival fails. Together the changes defend session truth against concurrent writes and partial persistence.
HKUDS/nanobot PR #5383 is the inspected primary source: “fix(session): serialize canonical file access.” The related records below were inspected as supporting context rather than independent confirmation.
The facts
- HKUDS/nanobot PR #5383 was created 2026-08-13T17:08:00Z and was open at inspection; its title is “fix(session): serialize canonical file access.” - HKUDS/nanobot PR #5382 was created 2026-08-13T16:22:12Z and was open at inspection; its title is “fix(session): retry os.replace() on transient Windows PermissionError.” - HKUDS/nanobot PR #5380 was created 2026-08-13T14:16:21Z and was open at inspection; its title is “fix(session): restore state when file-cap archive fails.” - The repository reported 46952 stars, 713 open issues, default branch main, and last push 2026-08-13T19:03:54Z. - The latest tagged-release baseline checked was v0.3.0 published 2026-07-25T08:08:47Z.
What changed
HKUDS/nanobot PR #5383 reports: Summary serialize canonical session JSONL readers and mutators behind one sessions-directory lock coordinate separate SessionManager instances that point at the same canonical directory use unique, exclusively created temp files while preserving atomic os.replace and existing fsync behavior route direct WebUI index scanning and Dream pruning through the store-owned boundary Root cause On Windows, an open reader for the destination JSONL file can deny os.replace(tmp, target) with [WinError 5] Access is denied. The confirmed production path was internal and deterministic: 1. /api/sessions ran _sessions_list_payload through asyncio.to_thread. 2. session_list_index._scan_session_row opened a canonical session JSONL file. 3. A peer turn completed and JsonlSessionStore.save tried to replace that same file. 4. Windows rejected the replacement, so a successfully streamed answer was followed by the generic error response. The store also used one predictable.jsonl.tmp, so concurrent saves could share scratch state. Design JsonlSessionStore now owns one FileLock at.session-files.lock per canonical sessions directory. Load/read/metadata/list/save/delete/repair and migration/restore use it. The exceptional direct consumers—WebUI index reconciliation and Dream pruning—enter the same boundary through a narrow SessionManager.locked_session_files() context manager. Different manager instances coordinate because they resolve the same lock path. Nested repair/delete operations reuse the same reentrant
HKUDS/nanobot PR #5382 reports: Summary JsonlSessionStore.save() (nanobot/session/manager.py) crashed the whole gateway when os.replace() hit a transient [WinError 5] Access is denied during the heartbeat cron job's session save. Confirmed twice in one gateway.log (2026-08-11 15:44 and 18:45 CDT) — the second occurrence correlates with the gateway going completely silent for 41+ hours with no auto-recovery (no Windows service/watchdog exists for this project, unlike the systemd/macOS install-service paths). os.replace() / MoveFileExW can raise PermissionError on Windows when another process (AV scanner, indexer, a concurrent reader) briefly holds a handle on the target file — there's no OS-level retry for this, unlike POSIX rename(). Fix Wrap the os.replace(tmp_path, path) call in save() with a short retry-with-backoff (5 attempts, exponential from a 0.05s base) on win32 only. POSIX behavior is unchanged — a single unconditional call, no retry needed there. Related to #5290 (atomic-write idiom consolidation across the three JSONL writers) — this fix is scoped only to the retry behavior in session/manager.py's save() and doesn't attempt that broader refactor, to avoid colliding with in-flight work on that issue. Test plan [x] New unit tests: retry recovers after N transient PermissionErrors, re-raises after exhausting attempts, no retry
HKUDS/nanobot PR #5380 reports: Summary snapshot the mutable session state before file-cap retention restore messages, consolidation cursor, provider state, and update timestamp when raw-history archival fails keep the failed prefix available so a later save can retry archival instead of persisting the trimmed session cover both direct rollback and manager-level retry behavior Fixes #5378 Validation uv run --no-sync ruff check nanobot tests conftest.py uv run --no-sync basedpyright uv run --no-sync pytest tests/agent/test_session_manager_history.py tests/session/test_session_store.py tests/agent/test_session_retention.py -q (66 passed)
Why it matters
Conversation history is control state. If two writers race, replacement fails transiently or archival mutates memory before durable commit, the next turn can inherit a history that never actually existed on disk. The operator test is whether visible state, retained state and authority still describe the same event after retries, switching or restart.
Current
The primary record was open when captured on 2026-08-14. The tagged-release baseline was v0.3.0 published 2026-07-25T08:08:47Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.
Evidence
The primary URL is https://github.com/HKUDS/nanobot/pull/5383. Supporting records are https://github.com/HKUDS/nanobot/pull/5382 and https://github.com/HKUDS/nanobot/pull/5380. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.
Source boundary
Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.
Operator take
Use one writer per canonical session, retry only bounded transient errors and roll back memory whenever persistence does not commit. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.
Caveat
Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.
Source inspected; source state, environment and release boundary remain explicit.