Claude Code desktop app closes instantly on launch — and Windows "Repair" doesn't fix it
# Claude Code desktop app closes instantly on launch — and Windows "Repair" doesn't fix it
Symptom, in the words you'd type at 1am: the Claude Code Windows app quits on its own. You restart it, Windows says it can't open. You run the installer's Repair. It opens for a fraction of a second and closes again. No error dialog. No crash log you can find. You search online and there is nothing recent that matches.
That was one evening on a solo Unreal Engine project. It took three separate sessions to get the app working again, and the thing that actually caused it was not any of the first three things that looked obviously guilty.
I'm writing this up with the false leads intact, because the false leads are the expensive part. If you only read the confident-sounding fix in a transcript afterwards, you will fix the wrong thing.
---
What actually caused it
A local dev server rendered in the app's in-app browser preview.
The app restores its previous view on launch. So the sequence is:
- During a work session, a script spun up a local web server and rendered it in the in-app browser preview.
- Something in that render took the app down.
- That preview state was saved as "where you were."
- On next launch the app dutifully restored it, reloaded the same local-server preview, and died again — before you could get to anything.
That is a self-perpetuating crash loop living in saved state. The install is not damaged. Nothing is corrupt. Which is precisely why Repair does nothing, and why a terminal session in the same project keeps working perfectly the whole time.
This one is confirmed by the developer. It is also still live — nothing at the config level prevents it happening again. See the prevention section at the bottom.
---
The three things that didn't work (and why each was believable)
1. Windows' own app Repair — no effect
The install was never damaged, so there was nothing to repair. Repair also refused to run at first, reporting that Claude was currently running — because a stale process was still alive (see below). Ending that task let Repair complete and report success, which made Repair look like the fix by pure coincidence of timing.
Lesson: "repair reported success and then it worked" is one of the easiest false attributions in Windows troubleshooting. Repair completing tells you Repair completed. It does not tell you Repair did anything.
2. Pruning the session-history folder — no effect, twice
This is the trap, and it's a good one, because the underlying observation is completely real.
The per-project transcript store for this project had grown to about 6.2 GB across ~3,600 files, with a single 102 MB transcript in it and six more between 43 and 52 MB. Disk was 93% full. It is entirely reasonable to look at that and conclude "the app is trying to enumerate/parse that on open and blowing its heap."
So the history was archived: everything older than 7 days moved out to a dated folder on the same volume (a rename, so instant and reversible — nothing deleted). 422 entries moved, folder down from 6.2 GB to about 1.19 GB, 94 resumable sessions kept.
Then the developer tried the app again and reported: "it wouldn't load."
That's the whole ballgame. The history theory was tested and failed. But it had already been asserted as "found it" mid-session, and it is the explanation anyone reconstructing this from the raw transcripts will land on. Which is why it's called out in the project's own notes as a red herring, in writing.
A related setting was also added — a 14-day cap on session history retention. Seven days later that setting has still never deleted a single file, because nothing in the store is old enough to qualify. Configured is not the same as working.
3. A GPU setting inside the app — no effect
Believable, because this genuinely does present as a rendering-layer crash. It is one. It just isn't the GPU config; it's what's being rendered.
---
The one thing that was confirmed on the way (worth knowing)
A stuck pre-update process was found alive: a desktop-entrypoint process running version 2.1.237, with its working directory set to the project root — while the installed binary had auto-updated to 2.1.239 about forty seconds before the diagnostic session started. It was holding a session marker file.
That process is definitely why the installer refused to repair. Whether it also contributed to the launch failures was never proven. It was ended via Task Manager, its orphaned session marker files were moved aside so a fresh launch started clean, and Repair then completed.
Also found and removed: a dual install — a stale global npm copy of the CLI package at 2.1.195 alongside the native binary at 2.1.239. Three versions in play on one machine. Not the cause, but worth cleaning up so which binary launches isn't PATH-order roulette.
---
What was never proved
Being straight about this, because the honest version is more useful:
- No crash artefact was ever examined. No Windows Event Log entry, no exit code, no heap/OOM message, no crash dump. The "it's blowing the Node heap on a huge history folder" mechanism was asserted, never observed.
- The three changes can't be separated by evidence alone. History trim, killing the stuck process, Repair — recovery coincided with the last two, not the first. The confirmed cause (restored preview state) came later, from the developer recognising what they'd been doing when it first died.
- Four unidentified node processes were present throughout and were never accounted for.
- ~237 transcripts all carried an identical sub-second modification time, meaning something rewrote their timestamps en masse at one instant. Noticed only because it broke a cleanup script. Never explained. Could be a sync agent or an indexer walking that folder.
---
Two debugging techniques that did earn their keep
The natural experiment. A terminal session launched from inside the project's own .claude subfolder ran fine the entire time — same machine, same app build, same config, but Claude keys it as a different project. That instantly told us: the binary is not broken, the config is not broken, the account is not broken. Something is scoped to that one project key. That narrowing is genuinely valuable even though the conclusion drawn from it at the time (history size) was wrong.
Launching from a system directory to test "can it start from anywhere at all" also worked — the CLI initialised fully, loaded its tool and skill registries, and accepted input. It then bounced the first turn with "Not logged in · Please run /login" and that experiment was abandoned. Useful negative result nonetheless: the executable starts fine from an arbitrary directory.
---
Bonus trap: two cleanup scripts that reported success while doing nothing
If you do decide to archive a big history folder, both of these bit:
Mutating a directory while streaming from find. Calling mv inside a loop that is still reading from find mutates the directory mid-readdir and entries get silently skipped. First run reported 55 files moved out of an expected several thousand, and reported it as a clean success — because the mv && counter++ idiom swallowed every failure. Snapshot the list before any mutation.
find -mtime +7 truncates to whole days. The bulk of the files were about 7.15 days old and therefore did not match +7. The script queued zero files, moved zero files, and reported "0 failed" — indistinguishable from a clean run. Use an exact timestamp instead:
find <dir> -maxdepth 1 ! -newermt "$(date -d '7 days ago')" ...
That version queued 422 entries and moved all 422.
Windows Python cannot open MSYS-style /c/... or /tmp/... paths. Two separate steps failed with FileNotFoundError when a Git Bash path was handed to the Windows Python build. Worse, in one case the shell's cp backup in the same command had succeeded, so it looked like a partial success rather than a failure. Use native drive-letter paths at the boundary.
tasklist /FI "PID eq N" returned nothing at all in that shell — including for a known-running control process. It confidently reported that both PIDs, including the live session's own, were dead. Always run a known-good control through a filter before you trust its negative result.
---
The unglamorous line about how this felt
Three attempts, an app that wouldn't open, and no search result anywhere describing it. The developer's own words afterwards: they were genuinely worried they would not be able to carry on developing the game. That is the actual cost of a crash loop in restored state — not the hour of debugging, the possibility that the project just stops.
---
What to do if you hit this
In order. Stop as soon as it opens.
- Do not reinstall. Do not run Repair first. The install is almost certainly fine, and Repair's success message will mislead you about what fixed it.
- Get a terminal Claude session running inside the project. It uses the same account and the same project context and it will almost certainly work. You are not blocked; you can keep developing while you sort the app out. This is the single most important step for your stress level.
- Suspect restored view state before you suspect anything else. Ask yourself: what was on screen when it first died? If the answer involves the in-app browser preview pointed at a
localhost/127.0.0.1dev server, that's your answer. The app reloads it on launch and re-crashes. - Check for a stuck process. Open Task Manager, look for a Claude process still alive — especially one on an older version than the one installed. End it. This also unblocks Repair if you end up wanting it. Clear any orphaned session marker files in the sessions directory so a fresh launch doesn't advertise a phantom instance.
- Check for a dual install. If both a native binary and a global npm copy of the CLI exist, remove the npm one so the launched binary isn't PATH-order dependent.
- Only then consider history size — and treat it as a disk-space and startup-latency issue, not a crash cause, unless you can produce an actual OOM message.
What to check BEFORE it happens to you
This cause is still live. Nothing at the config level stops it — only a written rule.
- Keep local dev servers out of the in-app browser preview. View them in a real external browser. If you have any automation that spins up a local server and renders it in-app, change it now.
- Consider a config-level guard, not just a note to self: a permission-deny entry covering the in-app preview/navigate tools for
localhostand127.0.0.1. A rule you have to remember is a rule you will forget at 11pm. - Remote/published URLs in the preview have not caused this — but that's "not yet observed", not "proven safe."
- Watch the growth rate of your project's transcript store, separately from all of the above. On this project it runs about 168 MB/day and hit 1.4 GB in seven days, dominated by inlined base64 screenshot payloads from editor MCP calls (~93% of all tool-result bytes; individual captures at 3828×1833 and similar). If your tooling returns images, the structural fix is to have captures written to disk and pass the path, rather than inlining base64 into the transcript. An age-based retention cap won't help you with a 64 MB file written today.
- Verify your retention setting has actually deleted something before you count it as protection. Ours has been configured for a week and has never yet had an eligible file.