claude-code - 💡(How to fix) Fix Registry file written non-atomically: Claude Desktop crash mid-write produces all-null `local_*.json`, session vanishes from picker (Windows 1.7196.0.0)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

No explicit crash log line was written (consistent with a hard kill / process exit during fsync rather than an exception).

Fix Action

Fix / Workaround

Anyone hitting this today has to do what I had to do manually: copy the entire JSON schema from another working session, hand-patch sessionId/cliSessionId/cwd/title/worktreePath/timestamps, and overwrite the null file. Far beyond what a normal user should be expected to do to recover their own conversation.

Code Example

2026-05-17 02:42:19  Last clean activity (different session, local_68bdc412...)
2026-05-17 02:45     local_<sid>.json mtime — file truncated to all-null
2026-05-17 02:46:14  [updater] Checking for updates  ← Claude Desktop relaunched
2026-05-17 02:46:14  [account] Account details not yet available, waiting for IPC...
2026-05-17 02:46:15  [MSIX] Filesystem virtualization active
2026-05-17 02:46:15  [growthbook] loaded 157 features (157 changed)
2026-05-17 02:46:15  [outbound-ccr] started

---

$b = [IO.File]::ReadAllBytes('local_<sid>.json')
$b.Length                                 # 144258
$first = -1
for ($i=0; $i -lt $b.Length; $i++) {
    if ($b[$i] -ne 0) { $first = $i; break }
}
$first                                    # -1  (no non-zero byte in entire file)

---

import json
from pathlib import Path

REG = Path(r"<...>\claude-code-sessions\<userId>\<orgId>")
TEMPLATE = REG / "local_<known-good-session>.json"
TARGET   = REG / "local_<corrupted-session>.json"

j = json.loads(TEMPLATE.read_text(encoding="utf-8"))
j["sessionId"]      = "local_<corrupted-session>"
j["cliSessionId"]   = "<uuid from corresponding .jsonl filename>"
j["cwd"]            = r"<worktree path>"
j["originCwd"]      = r"<repo root>"
j["worktreePath"]   = r"<worktree path>"
j["worktreeName"]   = "<worktree dir name>"
j["branch"]         = "claude/<worktree dir name>"
j["sourceBranch"]   = "main"
j["title"]          = "<custom title — recover from `customTitle` event in jsonl>"
j["titleSource"]    = "user"
j["isArchived"]     = False
j["createdAt"]      = <epoch ms>
j["lastActivityAt"] = <epoch ms>
TARGET.rename(TARGET.with_suffix(".json.corrupted-bak"))  # keep backup
TARGET.write_text(json.dumps(j, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
# then restart Claude Desktop (in-memory cache only refreshes at startup)
RAW_BUFFERClick to expand / collapse

TL;DR

Claude Desktop writes the per-session registry file (AppData/Roaming/Claude/claude-code-sessions/<userId>/<orgId>/local_<sessionId>.json) non-atomically. If the process dies during a flush, the OS leaves a file of the correct allocated size but filled entirely with null bytes (0x00). The session vanishes from every UI surface (/resume, session picker, CCD list_sessions MCP tool) — even though the underlying .jsonl transcript is fully intact.

This is the same bug class as #24958 / #24009 / #23421, but with concrete forensic evidence of the corruption pattern and a successful manual recovery procedure. Issue #24958 was closed as stale; the bug is still present in v1.7196.0.0.

Concrete incident (2026-05-17 ~02:45 ICT)

ArtifactState
local_<sid>.json registry entryCorrupted — 144,258 bytes, all 0x00
<cliSessionId>.jsonl conversation transcriptIntact — 38,518,504 bytes, valid JSONL
git-worktrees.json lease pointing at local_<sid>Intact — points to orphaned (now-unreadable) session
Session in list_sessions (incl. include_archived: true)Missing
Session in CLI /resumeMissing

User had this session pinned with a custom title. Both pin state and title were lost. The 38 MB of conversation history was unreachable until manual repair.

Timeline from AppData/Roaming/Claude/logs/main.log

2026-05-17 02:42:19  Last clean activity (different session, local_68bdc412...)
2026-05-17 02:45     local_<sid>.json mtime — file truncated to all-null
2026-05-17 02:46:14  [updater] Checking for updates  ← Claude Desktop relaunched
2026-05-17 02:46:14  [account] Account details not yet available, waiting for IPC...
2026-05-17 02:46:15  [MSIX] Filesystem virtualization active
2026-05-17 02:46:15  [growthbook] loaded 157 features (157 changed)
2026-05-17 02:46:15  [outbound-ccr] started

No explicit crash log line was written (consistent with a hard kill / process exit during fsync rather than an exception).

Forensic dump of the corrupted file

$b = [IO.File]::ReadAllBytes('local_<sid>.json')
$b.Length                                 # 144258
$first = -1
for ($i=0; $i -lt $b.Length; $i++) {
    if ($b[$i] -ne 0) { $first = $i; break }
}
$first                                    # -1  (no non-zero byte in entire file)

This is the classic Windows footprint of a non-atomic in-place write that lost its data page after the file was extended: the metadata (length) commits, but the data pages never reach disk before the process dies. Other writers using O_TRUNC + write + close produce the same artifact.

Why it breaks downstream

The session manager iterates claude-code-sessions/<userId>/<orgId>/local_*.json and tries JSON.parse on each file. A file of all NULs fails parse and is silently skipped — there's no warning, no telemetry event, no "would you like to recover?" prompt. The session is just gone from every UI.

The .jsonl transcript file is still on disk and valid — but with no registry entry, neither the picker nor claude --resume <uuid> finds it.

Requested fix

  1. Atomic write: fs.writeFile('local_<sid>.json.tmp', body)fs.rename('local_<sid>.json.tmp', 'local_<sid>.json'). On Windows, use MoveFileEx(src, dst, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) for atomicity across the metadata commit. Node's fs.promises.rename does this on NTFS.
  2. Crash recovery: on startup, if a registry file fails to parse, derive a stub entry from the matching <cliSessionId>.jsonl transcript (the first line of each transcript contains customTitle, aiTitle, and cwd events — enough to rebuild a basic registry entry). At minimum, show the user a recovery prompt instead of silently dropping the session.
  3. Surface the failure: log a warning when a local_*.json fails to parse, so the user can spot the problem before assuming the session is permanently lost.

Anyone hitting this today has to do what I had to do manually: copy the entire JSON schema from another working session, hand-patch sessionId/cliSessionId/cwd/title/worktreePath/timestamps, and overwrite the null file. Far beyond what a normal user should be expected to do to recover their own conversation.

Manual recovery procedure that worked

For anyone hitting this before the fix lands:

import json
from pathlib import Path

REG = Path(r"<...>\claude-code-sessions\<userId>\<orgId>")
TEMPLATE = REG / "local_<known-good-session>.json"
TARGET   = REG / "local_<corrupted-session>.json"

j = json.loads(TEMPLATE.read_text(encoding="utf-8"))
j["sessionId"]      = "local_<corrupted-session>"
j["cliSessionId"]   = "<uuid from corresponding .jsonl filename>"
j["cwd"]            = r"<worktree path>"
j["originCwd"]      = r"<repo root>"
j["worktreePath"]   = r"<worktree path>"
j["worktreeName"]   = "<worktree dir name>"
j["branch"]         = "claude/<worktree dir name>"
j["sourceBranch"]   = "main"
j["title"]          = "<custom title — recover from `customTitle` event in jsonl>"
j["titleSource"]    = "user"
j["isArchived"]     = False
j["createdAt"]      = <epoch ms>
j["lastActivityAt"] = <epoch ms>
TARGET.rename(TARGET.with_suffix(".json.corrupted-bak"))  # keep backup
TARGET.write_text(json.dumps(j, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
# then restart Claude Desktop (in-memory cache only refreshes at startup)

Pin state and exact completedTurns cannot be recovered.

Environment

  • OS: Windows 11
  • Claude Desktop: 1.7196.0.0 (MSIX install, Claude_pzs8sxrjxfjjc)
  • Filesystem: NTFS, OneDrive-synced parent (C:\Users\<USER>\OneDrive\Desktop\MycOS) — note: corrupted file is in AppData\Local\Packages\...\LocalCache\Roaming\Claude\, not under OneDrive, so OneDrive isn't a factor
  • Affected session: 38.5 MB .jsonl, ~6 months active

Related issues (closed as stale, not resolved)

  • #24958 — OOM crash corrupts session data: custom names lost and sessions missing from resume
  • #24009 — Crash corrupts session data, resume picker fails
  • #23421 — sessions-index.json stops updating, new sessions don't appear

Locking #24958 after 7 days of inactivity made it impossible to reply with new evidence to the same bug. Filing this as a fresh issue per the auto-close instruction.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Registry file written non-atomically: Claude Desktop crash mid-write produces all-null `local_*.json`, session vanishes from picker (Windows 1.7196.0.0)