claude --resume <sid> worked when we ran it in a terminal and returned No conversation found when our web app ran the exact same command, on the same machine, against a session file that demonstrably existed. The difference came down to one environment variable we never thought to set. The trap behind it is one every Windows service that shells out to a per-user CLI will eventually hit.

The setup

We run an agent dashboard. One of its jobs is to stream a Claude Code session into the browser: you click a session in the dashboard, and a bridge endpoint on the Node server spawns claude.exe --resume <sid> and pipes the output back to the page. The bridge itself was already built. Spawning, sending a message, fetching history, all wired. So when we hooked up the resume path and it came back with No conversation found, we expected a five-minute fix.

The server is a Node process registered as a Windows service through NSSM. That detail is the whole story, but we did not know that yet.

Confirming the obvious thing first

The first thing we did was confirm the session file actually existed, because No conversation found is exactly what you get when it does not. Claude Code stores each session as a JSONL transcript under your user home. The file was right there:

C:/Users/<username>/.claude/projects/<project>/1b169ea4-....jsonl

A 7 MB file, last modified at 11:59 PM that same night. Not missing, not empty. And the smoking gun: if we dropped into a terminal as the logged-in user and ran claude --resume 1b169ea4-..., it resumed fine. The CLI was not broken. The session was not corrupt. The only variable that changed between “works” and “fails” was who, and what, was launching the process.

That reframes the question entirely. The question becomes why Claude looks somewhere else when our service launches it.

How a CLI finds your home directory

Claude Code resolves its project store from the OS home directory. In Node terms that is os.homedir(), which on Windows reads the USERPROFILE environment variable. When the CLI runs in a user terminal, USERPROFILE is C:\Users\<username>, so the CLI looks in C:\Users\<username>\.claude\projects\ and finds everything.

When a process spawns a child, the child inherits the parent’s environment. So whatever USERPROFILE the app server has, the spawned claude.exe inherits. And the app server does not run as that user.

LocalSystem has a home, and it is not the user’s

NSSM-registered services, by default, run as the LocalSystem account. LocalSystem is a service account, not a human one, and it has its own profile. Its USERPROFILE is:

C:\Windows\system32\config\systemprofile

So the spawned CLI was doing exactly what it was told. It called os.homedir(), got C:\Windows\system32\config\systemprofile, and looked for the session in:

C:\Windows\system32\config\systemprofile\.claude\projects\

That directory is empty. Of course it is. We have never run Claude Code as LocalSystem. The file existed under the user profile and the CLI was looking under the service account’s profile, and both of those statements are simultaneously true, which is the part that makes this kind of bug hard to see. Nothing is missing. The lookup is just rooted in the wrong place.

The path variable that did not save us

Here is the part worth internalizing, because it is where we wasted time. The app already had a CLAUDE_PROJECTS_PATH style variable in its own environment, pointing at the real .claude/projects. So our first instinct was: the variable is set, the path is correct, why is the CLI ignoring it.

It was ignoring it because it never reads it. That variable is app-internal. The app server reads it to find transcripts for its own dashboard rendering. claude.exe has no idea that variable exists. The CLI resolves its home through os.homedir() and nothing else. An app-level path variable does not help you when the thing you are trying to redirect is a separate binary that resolves its own paths from the OS home.

This is the general trap. You can have every app-specific path pointing at the right place and still feed a child process the wrong home, because the child does not consult your app’s config. It consults USERPROFILE. If you do not override that on the spawn, you have fixed nothing.

The fix

Override USERPROFILE and HOME on the spawn environment, so the child CLI inherits a home that points at the real user profile instead of systemprofile:

spawn(CLAUDE_BIN, args, {
cwd,
env: { ...process.env, USERPROFILE: "C:\\Users\\<username>", HOME: "C:\\Users\\<username>" },
windowsHide: true,
})

We set both USERPROFILE and HOME. USERPROFILE is what Windows and os.homedir() use, but setting HOME too costs nothing and covers any tool with POSIX-flavored home resolution. Spread process.env first so you keep everything else the service already has, then stomp the two home vars on top.

That made resume work immediately. But a per-spawn override only fixes the running process. To make it survive a service restart, we baked a CLAUDE_USER_HOME=C:/Users/<username> style value into the NSSM service config through AppEnvironmentExtra, so the override is part of the service environment and not just a code-path that has to remember to set it. Belt and suspenders: the spawn sets the home explicitly, and the service-level env carries the canonical user-home value so nothing downstream has to guess.

How to confirm you are actually in this hole

If you suspect this, do not theorize, check. From inside the service process, print process.env.USERPROFILE (or os.homedir()). If it comes back C:\Windows\system32\config\systemprofile, you are running as LocalSystem and every per-user path your child processes resolve is pointed at the service profile.

The other tell is the asymmetry that started this whole thing: it works in your terminal and fails from the service. Any time a command behaves differently launched by a service than launched by you at a prompt, environment inheritance is the first suspect, and on Windows the home directory is the most common thing that differs. NSSM writes the service stderr to whatever path you set in AppStderr (nssm get <service> AppStderr tells you where). When the spawn itself is silent, go read that log to confirm the child even launched before you chase the path.

Takeaway

A Windows service that spawns a per-user CLI hands that CLI the home directory C:\Windows\system32\config\systemprofile by default, so the CLI reports that nothing is there. Override USERPROFILE and HOME on the spawn environment, because the child binary reads the OS home and not your app’s own path variables. When a command works at a prompt and fails from a service, check the service’s USERPROFILE first.