[BUG] Desktop (Linux ARM64): session registry saves fail with EINVAL — sessions vanish from sidebar on restart
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
On the Claude Desktop app for Linux ARM64, every attempt to persist a session's sidebar entry fails with EINVAL. Sessions work normally while the app is running, but exist only in memory — after an app restart they vanish from the sidebar entirely (they do not appear under the Archived filter either). The failure is silent; nothing is surfaced to the user, so it presents as unexplained loss of session history. I lost a full overnight working session from the sidebar this way.
Root cause (verified on the affected machine): the storage-dir verification in the app bundle ($r / mkdirPrivate in index.chunk-Cv8AY_mc.js, called from ensureStorageDir → writeSessionToDisk in index2.chunk-BXE3SynL.js) opens the directory with O_RDONLY | O_DIRECTORY | O_NOFOLLOW, but the flag values used are x86-64 values, not aarch64 values. On aarch64, x86-64's O_DIRECTORY bit (0o200000) means O_DIRECT, and open() with O_DIRECT on a directory returns exactly EINVAL (errno -22).
Verified empirically against the actual storage directory:
- os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) → native aarch64 flags: succeeds
- os.open(path, os.O_RDONLY | 0o200000 | 0o400000) → x86-64 flag values: [Errno 22] Invalid argument
The claude-desktop binary is a native aarch64 ELF and system Node reports correct aarch64 fs.constants, so the bundle's constants object is presumably a bundler shim (constants-browserify-style) hardcoding x86-64 Linux values instead of runtime fs.constants.
The filesystem itself is ext4, healthy, and writable (verified by creating a file in the same directory).
Suggested fix: use runtime require('fs').constants (per-platform correct) in the mkdirPrivate verification, or skip unsupported bits.
What Should Happen?
Session sidebar entries should be written to ~/.config/Claude/claude-code-sessions/<accountId>/<orgId>/local_<id>.json on every save, so sessions persist in the sidebar across app restarts — as they did before the update. If a save fails repeatedly, the user should be warned instead of silently losing session history.
Error Messages/Logs
From ~/.config/Claude/logs/main.log — repeated on every save (73 occurrences since the update):
[error] Failed to save session local_a8741449-3800-4faf-b18d-ebed017d1993: EINVAL: invalid argument, open '/home/parallels/.config/Claude/claude-code-sessions/<accountId>/<orgId>' { errno: -22, code: 'EINVAL', syscall: 'open',
stack: "Error: EINVAL: invalid argument, open '...'
at async open (node:internal/fs/promises:640:25)
at async Object.$r (/usr/lib/claude-desktop/resources/app.asar/.vite/build/index.chunk-Cv8AY_mc.js:1:265681)
at async e.ensureStorageDir (/usr/lib/claude-desktop/resources/app.asar/.vite/build/index2.chunk-BXE3SynL.js:1:310044)
at async e.writeSessionToDisk (/usr/lib/claude-desktop/resources/app.asar/.vite/build/index2.chunk-BXE3SynL.js:1:318136)" }
Last successful write of a local_*.json entry in that directory: 2026-09-01 08:26. All writes fail from 2026-09-01 09:07 onward — the exact minute the embedded Claude Code runtime updated from 2.1.237 to 2.1.247.
Steps to Reproduce
- Run the Claude Desktop app (deb, arm64) on an aarch64 Linux machine (mine: Ubuntu VM under Parallels on Apple Silicon).
- Start a new Claude Code session in any project and exchange a few messages.
- Observe ~/.config/Claude/logs/main.log: every save logs "Failed to save session ... EINVAL: invalid argument, open '.../claude-code-sessions/<accountId>/<orgId>'".
- Quit and reopen the desktop app.
- The session is gone from the sidebar (also absent from the Archived filter). The CLI transcript still exists under ~/.claude/projects/.
Minimal reproduction of the underlying syscall bug on any aarch64 Linux box:
python3 -c "import os; os.open(os.path.expanduser('~'), os.O_RDONLY | 0o200000 | 0o400000)"
→ EINVAL, because 0o200000 (x86-64 O_DIRECTORY) is O_DIRECT on aarch64.
Claude Model
Other
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Embedded Claude Code runtime 2.1.237 (saves worked until the in-place update to 2.1.247 on 2026-09-01)
Claude Code Version
2.1.247 (embedded in Claude Desktop 1.40609.0, deb arm64)
Platform
Other
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
- Data is recoverable: transcripts survive in ~/.claude/projects/<project>/<cliSessionId>.jsonl and resume fine with
claude --resume <cliSessionId>. Only the desktop sidebar registry entries are lost. - Sidebar entries can be reconstructed by hand-writing local_<id>.json files (sessionId + cliSessionId + cwd + timestamps) into ~/.config/Claude/claude-code-sessions/<accountId>/<orgId>/ — the app then shows the session again after restart, confirming only the write path is broken.
- Because writes fail before touching existing files, previously saved sessions from before the update still list correctly — only sessions created or modified after the update are affected.
4 Comments
Confirming this on a second, independent machine — and with one correction to the "last working version", because the trigger looks like the desktop app upgrade rather than the embedded CLI update.
Environment:
claude-desktop1.40609.0, arm64.deb, Ubuntu onLinux 6.17.0-1020-oracle(bare-metal Ampere, not a Parallels VM),$HOMEon ext4 (rw,relatime,discard,errors=remount-ro,commit=30). Identical stack, same silent failure, same result: two multi-hour sessions (97 and 73 turns) gone from the sidebar.163 EINVALs in one day, 84 of them
Failed to save session. Every launch since logsLoaded 1 persisted sessions— that one entry is from 2026-08-21, i.e. everything after it is unreachable from the UI.The 2.1.247 update is not the trigger
My box never completed the 2.1.247 update and is still running 2.1.237, yet saves are broken anyway:
~/.config/Claude/claude-code/2.1.247/was created at03:05and is empty —[CCD] Download attempt 1/3 … 2/3 … 3/3 failed,[CCD] All download attempts failed, all with the same EINVAL on the version directory.~/.config/Claude/claude-code/2.1.237/still holds the real.payload/.verified, and every transcript written since records"version":"2.1.237".What did change at that moment is the app itself.
main.logis unrotated back to 2026-08-21 and contains zero EINVALs before this line:apt upgraded the package on 2026-08-29 08:33 (
upgrade claude-desktop:arm64 1.34493.1 1.40609.0); 03:05 on 09-03 was simply the first launch on it. So the failing constants arrived with claude-desktop 1.40609.0, and the[CCD] Initialized with version 2.1.247line is the app intending to use 2.1.247, not evidence that it did. Worth adjusting "Last Working Version" to the app version — the CLI is a red herring, and chasing the 2.1.237→2.1.247 diff would miss it.This is consistent with your aarch64 flag analysis, which I think is the correct explanation. I arrived at O_DIRECT independently by probing the failing directory before finding this issue, and your reading of why O_DIRECT is being passed subsumes mine — I had no explanation for why the app would deliberately set it on a directory:
| flags | result |
|---|---|
|
O_RDONLY \| O_DIRECTORY(native aarch64) | OK ||
O_RDONLY \| O_SYNC| OK ||
O_TMPFILE \| O_RDONLY| OK ||
O_WRONLY \| O_DIRECTORY| EISDIR ||
O_RDWR \| O_DIRECTORY| EISDIR ||
O_RDONLY \| O_CREAT| EISDIR ||
O_TMPFILE \| O_RDWR| EISDIR ||
O_RDONLY \| O_DIRECT| EINVAL |mkdir(recursive)andwriteFile()into the same directory both succeed, so the directory is fine — only that open fails. Aligns with #90359'sstrace(O_RDONLY|O_DIRECT|O_LARGEFILE|O_CLOEXEC→ EINVAL) and with #90703's Electron-constants report; the three look like one root cause surfacing in different subsystems.Reads are unaffected, so the lost sessions are recoverable
Worth noting for anyone hitting this, since the data-loss label is what makes it urgent: nothing is actually lost. The CLI transcripts under
~/.claude/projects/<slug>/*.jsonlare complete, and only the desktop index is missing. Rebuildinglocal_<uuid>.jsonfrom a transcript restores the sidebar entry and resume works —cliSessionIdis the field that matters,custom-title/cwd/ timestamps all come out of the transcript, and the app's ownMapping internal session local_X to CLI session Ylog lines recover the original pairing. Load path never touchesensureStorageDir, so hand-written entries survive.That also suggests a cheap mitigation independent of the flag fix: when an index entry is missing but a transcript exists, reconstruct it on load instead of dropping the session — plus surface a warning when persistence has been failing, rather than discovering it at restart.
The evidence separates the layers cleanly:
EINVALis in Desktop's registry write path, while the JSONL transcript survives andclaude --resumeremains usable. That is a missing durable session-list record, not a deleted transcript.I built a toolkit for this file-layer distinction:
diagnose.pyinBasedGPT/claude-code-session-recoveryreads the platform's metadata and~/.claude/projects/transcript layers and prints a route when the state matches a supported case. Run it read-only first on the ARM64 machine and review its platform paths; do not apply a generated metadata command until it confirms the exact Linux account/organisation directory.Keep copies of the JSONL files and
main.log. If diagnosis prints a repair route, fully quit Desktop and review the dry-run before any apply; if it stays audit-only, leave the state unchanged. TheEINVALfix itself belongs upstream: use runtimefs.constantsrather than hard-coded x86 flags.Hope this helps, if my tools are able to help you, would appreciate a ⭐ :)
Hey dhruvkej9,
The second machine is useful because it separates the Desktop app upgrade from the embedded CLI version:
EINVALstill appears while the older CLI remains in use, and the transcripts remain resumable. That leaves the Desktop registry write path as the file-layer boundary.I built a toolkit for this distinction:
diagnose.pyinBasedGPT/claude-code-session-recoveryinventories metadata and transcript files and prints the exact route for orphaned sessions. Run it read-only on the affected Linux machine, review what it finds, and preserve the JSONL files before considering any manual metadata rebuild. The diagnosis should establish whether each missing sidebar entry has a matching transcript; it cannot correct the aarch64 flag constants inside Desktop.Hope this helps, if my tools are able to help you, would appreciate a ⭐ :)
@bcherny This is a critical bug, can we expect an fix for this?