Background shells are killed on the next user message, and PowerShell background commands at end of turn (startsBackgroundWork / superseded run)
Summary
Two gaps in the background-work hold from #1113, both separate from #1268 (which covers the Agent tool):
PowerShellis not recognised bystartsBackgroundWork(). On Windows, Claude Code's primary shell tool isPowerShell, notBash. APowerShellcall withrun_in_background: trueis treated as "nothing outstanding", stdin is released onresult, and the background command is killed at the end of the same turn.- The next user message kills background work that is correctly held.
queryClaudeSDK()starts a new query (a newclaude --resumeprocess) for every turn and immediately releases the previous run's held stdin (getSession(sessionKey())?.releaseInput?.()), andaddSession()callsinterrupt()on the superseded instance. The old CLI reads EOF, enters print wind-down and kills its background shells. So evenBashwithrun_in_background: trueonly survives until the user sends the next message.
Either way, the next turn opens with:
<status>stopped</status>
<summary>Background shell command didn't finish before the previous session ended</summary>Reproduce
Gap 1 (PowerShell, Windows):
- Ask Claude to run, via the
PowerShelltool withrun_in_background: true, a loop that appends a timestamp to a log every 5 s for 3 minutes. - Let the turn end. Don't send anything.
- The log stops growing within ~1 s of the turn's
result.
Measured: turn ended ~23:46:20, last log line 23:46:22. For control, two loggers started from the same turn with Start-Process (hidden) and WMI Win32_Process.Create, which are outside the CLI's lifecycle, kept writing through the turn end and through the next message.
Gap 2 (any held background work):
- Ask Claude to start a long-running dev server with
Bashandrun_in_background: true(e.g.pnpm dev). The hold works: the process stays alive after the turn ends. - Send any follow-up message.
- The dev server is gone, and the new turn opens with the
stopped … previous session endednotification.
Seen in the transcripts: turn ended 23:45:25, follow-up sent 23:47:30, and stopped on pnpm dev right at the start of the follow-up turn. Across one machine's history, 46 such notifications in 12 sessions over one week, all background shells (dev servers, wait loops).
Cause
server/modules/providers/list/claude/claude-runtime.provider.js (1.37.3):
function startsBackgroundWork(sdkMessage) {
...
if (block.name === 'Bash') {
return block.input?.run_in_background === true;
}
return DEFERRED_WORK_TOOLS.has(block.name); // no PowerShell arm
}// queryClaudeSDK(): a new turn supersedes any earlier one still holding this session's process open
if (sessionKey()) {
getSession(sessionKey())?.releaseInput?.();
}// addSession(): superseding run
Promise.resolve().then(() => existing.instance.interrupt());
existing.releaseInput?.();The comment above the release says held runs should not "stack up across a conversation", which is reasonable. But because each turn is a separate process, superseding the process also kills everything that turn left running in the background.
Suggested fix
- Mirror the
Basharm forPowerShell:if (block.name === 'Bash' || block.name === 'PowerShell') { return block.input?.run_in_background === true; } - While a run is
heldForBackgroundWork, feed the next user message into that run's held prompt stream (it is already a streaming-input query) instead of starting a new query and releasing/interrupting the old one. This is the same direction #884 suggests: route the next turn to the live process for the sid. Background tasks then report back in the same process, and nothing is killed on the user's next message.
Environment
- cloudcli 1.37.3, installed globally via npm (file verified byte-identical to the published tarball, no local patches)
- Claude Code 2.1.270
- Node v22.16.0, Windows 11 Pro (26200)
Related: #1113, #1268, #884
Source: siteboon/claudecodeui