diff --git a/README.md b/README.md
index 29db39b..7afe853 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ See what needs attention, resume the right conversation, and work on a paired PC
-> **1.47.0** moves to Electron 44 (Chromium 152, Node.js 24); macOS 13 or later is now required, and screenshot paste / image copy use Electron's new clipboard API ([release notes](docs/releases/1.47.0.md)). 1.46.0 lets you close Claude Code's background sessions from the sidebar — the row's ⋯ menu or the group's bulk close runs `claude stop` and `claude rm` for you, so a finished or failed session no longer sits in the Background group forever; its conversation is kept ([release notes](docs/releases/1.46.0.md)). 1.45.2 stops an update from doubling the sessions on a paired PC — the relaunch no longer restores remote tiles itself, it adopts them from that PC, and no restore starts a second conversation for one that is already open here ([release notes](docs/releases/1.45.2.md)). 1.45.1 stops offering Claude background sessions whose folder was deleted, and says so plainly when a session's folder is gone ([release notes](docs/releases/1.45.1.md)). 1.45.0 sends any file to a session on another PC — any kind, up to 4 GB, in pieces with its progress shown ([release notes](docs/releases/1.45.0.md)). 1.44.1 fixes the "Assertion failed: remove_pty_baton" dialog (and the occasional hang or crash) when quitting or running Update all with several sessions open ([release notes](docs/releases/1.44.1.md)). 1.44.0 lists Claude Code's own background sessions (`claude --bg`, the agent view) in the sidebar, marks the ones waiting for you, and opens one in a tile by attaching to it ([release notes](docs/releases/1.44.0.md)). 1.43.0 runs parallel sessions in separate workspaces — each in its own git worktree and branch, so two agents never edit the same files — with its changes in the header and a clean-up that warns before unmerged work is lost ([release notes](docs/releases/1.43.0.md)). 1.42.0 shows what changed — a What's new window after each update, with Show me, and NEW dots on new buttons until you try them — and, as an experimental setting on Windows, lets Claude Code report its own state (working, needs approval, asking, done) instead of DevDeck reading the screen ([release notes](docs/releases/1.42.0.md)). 1.41.0 adds quick replies (one click or Ctrl+Shift+1…9 for the phrases you keep typing), a session timeline of your prompts with their times, replies and images, red boxes and arrows drawn on a screenshot and sent straight back to the session, and text, log and PDF attachments for sessions on another PC ([release notes](docs/releases/1.41.0.md)). 1.40.0 ties each screenshot to the terminal row it appeared on — hover a highlighted `[Image]` for a thumbnail, click for the full view, and jump from the viewer back to the row — says what led to each image, previews new screenshots, copies images, and adds Ctrl+↑/↓ between your prompts ([release notes](docs/releases/1.40.0.md)). 1.39.0 shows the screenshots and images a Claude session worked with — Claude Desktop shows them inline; DevDeck puts them in a strip beside the terminal, also for sessions on another PC — adds 📎 / drag-and-drop attachments (images travel to a remote PC), **Update all** for Claude Code on Windows, staggered session restores that no longer stall a PC, and ends the daily false "Login expired" ([release notes](docs/releases/1.39.0.md)). 1.38.0 made closing a session reliable across paired PCs ([notes](docs/releases/1.38.0.md)). See [the maintenance review](docs/quality-review-2026-09-08.md) and [session synchronization review](docs/session-sync-review-2026-09-08.md).
+> **1.47.1** makes closing a Claude Code background session from the sidebar work when Claude Code's background service is not running — DevDeck starts it as Claude Code does and tries again, and a failure names the PC the command has to run on ([release notes](docs/releases/1.47.1.md)). 1.47.0 moves to Electron 44 (Chromium 152, Node.js 24); macOS 13 or later is now required, and screenshot paste / image copy use Electron's new clipboard API ([release notes](docs/releases/1.47.0.md)). 1.46.0 lets you close Claude Code's background sessions from the sidebar — the row's ⋯ menu or the group's bulk close runs `claude stop` and `claude rm` for you, so a finished or failed session no longer sits in the Background group forever; its conversation is kept ([release notes](docs/releases/1.46.0.md)). 1.45.2 stops an update from doubling the sessions on a paired PC — the relaunch no longer restores remote tiles itself, it adopts them from that PC, and no restore starts a second conversation for one that is already open here ([release notes](docs/releases/1.45.2.md)). 1.45.1 stops offering Claude background sessions whose folder was deleted, and says so plainly when a session's folder is gone ([release notes](docs/releases/1.45.1.md)). 1.45.0 sends any file to a session on another PC — any kind, up to 4 GB, in pieces with its progress shown ([release notes](docs/releases/1.45.0.md)). 1.44.1 fixes the "Assertion failed: remove_pty_baton" dialog (and the occasional hang or crash) when quitting or running Update all with several sessions open ([release notes](docs/releases/1.44.1.md)). 1.44.0 lists Claude Code's own background sessions (`claude --bg`, the agent view) in the sidebar, marks the ones waiting for you, and opens one in a tile by attaching to it ([release notes](docs/releases/1.44.0.md)). 1.43.0 runs parallel sessions in separate workspaces — each in its own git worktree and branch, so two agents never edit the same files — with its changes in the header and a clean-up that warns before unmerged work is lost ([release notes](docs/releases/1.43.0.md)). 1.42.0 shows what changed — a What's new window after each update, with Show me, and NEW dots on new buttons until you try them — and, as an experimental setting on Windows, lets Claude Code report its own state (working, needs approval, asking, done) instead of DevDeck reading the screen ([release notes](docs/releases/1.42.0.md)). 1.41.0 adds quick replies (one click or Ctrl+Shift+1…9 for the phrases you keep typing), a session timeline of your prompts with their times, replies and images, red boxes and arrows drawn on a screenshot and sent straight back to the session, and text, log and PDF attachments for sessions on another PC ([release notes](docs/releases/1.41.0.md)). 1.40.0 ties each screenshot to the terminal row it appeared on — hover a highlighted `[Image]` for a thumbnail, click for the full view, and jump from the viewer back to the row — says what led to each image, previews new screenshots, copies images, and adds Ctrl+↑/↓ between your prompts ([release notes](docs/releases/1.40.0.md)). 1.39.0 shows the screenshots and images a Claude session worked with — Claude Desktop shows them inline; DevDeck puts them in a strip beside the terminal, also for sessions on another PC — adds 📎 / drag-and-drop attachments (images travel to a remote PC), **Update all** for Claude Code on Windows, staggered session restores that no longer stall a PC, and ends the daily false "Login expired" ([release notes](docs/releases/1.39.0.md)). 1.38.0 made closing a session reliable across paired PCs ([notes](docs/releases/1.38.0.md)). See [the maintenance review](docs/quality-review-2026-09-08.md) and [session synchronization review](docs/session-sync-review-2026-09-08.md).

diff --git a/docs/releases/1.47.1.md b/docs/releases/1.47.1.md
new file mode 100644
index 0000000..df3f815
--- /dev/null
+++ b/docs/releases/1.47.1.md
@@ -0,0 +1,11 @@
+# DevDeck 1.47.1
+
+## Closing a background session works when Claude Code's background service is off
+
+Closing a Claude Code background session from the sidebar (1.46.0) could fail with "Could not close the background session '…' — try claude rm
in a terminal", on this PC and on a paired one alike.
+
+- **Cause:** `claude stop` and `claude rm` talk to Claude Code's background service, an on-demand daemon that exits 50 s after its last client — and a Claude Code update leaves none behind. With no service running, Claude Code 2.1.312 answers `couldn't remove — the background service may be restarting. Try again in a moment.` and does not start one. Trying again in a moment does not help; opening `claude agents` once does, because that starts the service. DevDeck's log only said `failed`; the reason was on stderr, which it did not read.
+- **Fix:** when `rm` refuses, DevDeck starts the service the way Claude Code itself does (`claude daemon run --origin transient` — it ends itself once idle, so nothing is left running), waits for `claude daemon status` to see it, and runs `stop` and `rm` once more. A session that is already gone counts as closed. What `rm` said is now in the diagnostics log.
+- **The message names the right PC.** The failure toast told you to run `claude rm ` "in a terminal" even when the session was on another PC, where the command has to run; on this PC it answered `No job matching`. It now names that PC, and says that opening `claude agents` there once starts the service.
+
+Verified on Windows with Claude Code 2.1.312: with the service stopped, `stop` and `rm` on a session waiting for input both refused with the message above; after `claude daemon run` they answered `stopped ` and `removed `; the transient service exited on its own 50 s later. A paired PC gets the fix when its DevDeck updates.
diff --git a/package-lock.json b/package-lock.json
index 9e28bcc..a33d9d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "devdeck",
- "version": "1.47.0",
+ "version": "1.47.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "devdeck",
- "version": "1.47.0",
+ "version": "1.47.1",
"license": "MIT",
"dependencies": {
"@fontsource-variable/geist": "5.3.0",
diff --git a/package.json b/package.json
index 416aa17..6d33778 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "devdeck",
- "version": "1.47.0",
+ "version": "1.47.1",
"description": "Project command deck — at-a-glance state + Claude Code, Codex, and Antigravity resume",
"main": "dist/main/main.js",
"type": "commonjs",
diff --git a/src/main/api/deckApi.ts b/src/main/api/deckApi.ts
index 01091ce..30ee3b8 100644
--- a/src/main/api/deckApi.ts
+++ b/src/main/api/deckApi.ts
@@ -1137,7 +1137,8 @@ export function createDeckApi(cfg: DeckApiConfig): DeckApiBundle {
const folders = effFolders();
const listed = (await listBackgroundAgents()).filter((b) => isAllowedFilePath(folders, b.cwd));
const result = await closeBackgroundAgent(id, listed);
- cfg.diagnostics?.write(result.ok ? 'info' : 'warn', 'agents', `close background ${String(id).slice(0, 16)}: ${result.ok ? 'removed' : result.error}`);
+ const how = result.ok ? `removed${result.serviceStarted ? ' (after starting the background service)' : ''}` : `${result.error}${result.detail ? ` (${result.detail})` : ''}`;
+ cfg.diagnostics?.write(result.ok ? 'info' : 'warn', 'agents', `close background ${String(id).slice(0, 16)}: ${how}`);
return result;
});
diff --git a/src/main/backgroundAgents.test.ts b/src/main/backgroundAgents.test.ts
index 34be5d4..2873070 100644
--- a/src/main/backgroundAgents.test.ts
+++ b/src/main/backgroundAgents.test.ts
@@ -1,7 +1,29 @@
+import { EventEmitter } from 'node:events';
import { afterEach, describe, expect, it, vi } from 'vitest';
const spawn = vi.fn();
vi.mock('node:child_process', () => ({ spawn: (...a: unknown[]) => spawn(...a) }));
+// A bare `claude` keeps cliInvocation from wrapping the arguments in cmd.exe on Windows, so the
+// tests can read the argv as written.
+vi.mock('./cliExecutable', () => ({ resolveAgentCliPath: () => 'claude' }));
+
+type Answer = { code: number; stderr?: string };
+/** A `claude` that answers each invocation by its first words, like the real one on the PC. */
+function fakeClaude(answer: (args: string[]) => Answer): void {
+ spawn.mockImplementation((_file: string, args: string[]) => {
+ const child = Object.assign(new EventEmitter(), { stdout: new EventEmitter(), stderr: new EventEmitter(), pid: 4242, kill: () => true, unref: () => undefined });
+ const a = answer(args);
+ setTimeout(() => {
+ if (a.stderr) child.stderr.emit('data', Buffer.from(a.stderr));
+ child.emit('close', a.code);
+ }, 0);
+ return child;
+ });
+}
+const calls = (): string[] => spawn.mock.calls.map((c) => (c[1] as string[]).join(' '));
+
+const LISTED = [{ id: '8e64da40', cwd: 'C:\\g\\shipwright', sessionId: '8e64da40-4c50-4e80-ae72-bddba02e6bcb', name: 'triage', state: 'blocked' as const, startedAt: 1 }];
+const SERVICE_DOWN = "couldn't remove 8e64da40 — the background service may be restarting. Try again in a moment.";
describe('listing background sessions while Claude Code is being updated', () => {
afterEach(async () => { const m = await import('./backgroundAgents.js'); await m.pauseBackgroundAgents(false); m._clearBackgroundAgentCache(); spawn.mockReset(); });
@@ -13,3 +35,54 @@ describe('listing background sessions while Claude Code is being updated', () =>
expect(spawn).not.toHaveBeenCalled();
});
});
+
+describe('closing a background session when the background service is not running', () => {
+ afterEach(() => { spawn.mockReset(); });
+
+ it('stops and removes in one go while the service is up — nothing else is started', async () => {
+ fakeClaude(() => ({ code: 0 }));
+ const m = await import('./backgroundAgents.js');
+ expect(await m.closeBackgroundAgent('8e64da40', LISTED)).toEqual({ ok: true });
+ expect(calls()).toEqual(['stop 8e64da40', 'rm 8e64da40']);
+ });
+
+ it('starts the service the way Claude Code does and tries again when rm says it may be restarting', async () => {
+ let serviceUp = false;
+ fakeClaude((args) => {
+ if (args[0] === 'daemon' && args[1] === 'run') { serviceUp = true; return { code: 0 }; }
+ if (args[0] === 'daemon' && args[1] === 'status') return { code: serviceUp ? 0 : 1 };
+ if (!serviceUp) return { code: 1, stderr: SERVICE_DOWN };
+ return { code: 0 };
+ });
+ const m = await import('./backgroundAgents.js');
+ expect(await m.closeBackgroundAgent('8e64da40', LISTED, { serviceWaitMs: 200 })).toEqual({ ok: true, serviceStarted: true });
+ expect(calls()).toEqual(['stop 8e64da40', 'rm 8e64da40', 'daemon run --origin transient', 'daemon status', 'stop 8e64da40', 'rm 8e64da40']);
+ // The service runs on after DevDeck, and on its own: detached, with no pipes to this process.
+ const run = spawn.mock.calls.find((c) => (c[1] as string[])[1] === 'run')!;
+ expect(run[2]).toMatchObject({ detached: true, stdio: 'ignore' });
+ });
+
+ it('reports what rm said when the service never comes up — the message goes to the log, not away', async () => {
+ fakeClaude((args) => (args[0] === 'daemon' && args[1] === 'run' ? { code: 0 } : { code: 1, stderr: SERVICE_DOWN }));
+ const m = await import('./backgroundAgents.js');
+ const result = await m.closeBackgroundAgent('8e64da40', LISTED, { serviceWaitMs: 50 });
+ expect(result).toMatchObject({ ok: false, error: 'failed' });
+ expect((result as { detail?: string }).detail).toContain('may be restarting');
+ expect(calls().filter((c) => c === 'rm 8e64da40')).toHaveLength(1); // no second try without a service
+ });
+
+ it('counts a session that is already gone as closed', async () => {
+ fakeClaude((args) => (args[0] === 'rm' ? { code: 1, stderr: "No job matching '8e64da40'" } : { code: 1 }));
+ const m = await import('./backgroundAgents.js');
+ expect(await m.closeBackgroundAgent('8e64da40', LISTED)).toEqual({ ok: true });
+ expect(calls()).toEqual(['stop 8e64da40', 'rm 8e64da40']);
+ });
+
+ it('acts only on an id the listing shows, and only on a short hex one', async () => {
+ fakeClaude(() => ({ code: 0 }));
+ const m = await import('./backgroundAgents.js');
+ expect(await m.closeBackgroundAgent('00cf1408', LISTED)).toEqual({ ok: false, error: 'not-listed' });
+ expect(await m.closeBackgroundAgent('8e64da40; rm -rf /', LISTED)).toEqual({ ok: false, error: 'bad-id' });
+ expect(spawn).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/main/backgroundAgents.ts b/src/main/backgroundAgents.ts
index 31bd6bb..1984e3a 100644
--- a/src/main/backgroundAgents.ts
+++ b/src/main/backgroundAgents.ts
@@ -48,7 +48,9 @@ function run(): Promise {
return runClaude(['agents', '--json', '--all'], TIMEOUT_MS, (child) => { current = child; });
}
-export type BackgroundCloseResult = { ok: true } | { ok: false; error: 'bad-id' | 'not-listed' | 'failed' };
+export type BackgroundCloseResult =
+ | { ok: true; serviceStarted?: boolean }
+ | { ok: false; error: 'bad-id' | 'not-listed' | 'failed'; detail?: string };
/**
* Close one of Claude Code's background sessions: `claude stop ` (ends it if it is still running;
@@ -60,34 +62,100 @@ export type BackgroundCloseResult = { ok: true } | { ok: false; error: 'bad-id'
* (Claude Code 2.1.311): `stop` prints "stopped ", `rm` prints "removed " and the
* `~/.claude/jobs/` folder is gone.
*
+ * Both commands talk to Claude Code's background service (its on-demand daemon). When it is not
+ * running — the usual state: it exits 50 s after its last client, and a Claude Code update leaves
+ * none behind — `stop` and `rm` can answer "couldn't remove — the background service may be
+ * restarting. Try again in a moment." without starting one (seen on Claude Code 2.1.312, on this
+ * PC and on a paired one). Trying again in a moment does not help; opening `claude agents` once
+ * does, because that starts the service. So when `rm` refuses, DevDeck starts the service the way
+ * Claude Code itself does (`claude daemon run --origin transient`, which exits again on its own
+ * once idle) and runs the two commands once more.
+ *
* Only an id the listing currently shows is acted on: the id is the one thing put on the command
* line, and this keeps a remote deck from naming sessions it was never shown.
*/
-export async function closeBackgroundAgent(id: unknown, listed: readonly BackgroundAgent[]): Promise {
+export async function closeBackgroundAgent(id: unknown, listed: readonly BackgroundAgent[], opts: { serviceWaitMs?: number } = {}): Promise {
if (!isBackgroundAgentId(id)) return { ok: false, error: 'bad-id' };
if (!listed.some((b) => b.id === id)) return { ok: false, error: 'not-listed' };
- await runClaude(['stop', id], CLOSE_TIMEOUT_MS).catch(() => ''); // already exited → nothing to stop
- const removed = await runClaude(['rm', id], CLOSE_TIMEOUT_MS).then(() => true, () => false);
+ let refusal = await stopAndRemove(id);
+ let serviceStarted = false;
+ if (refusal !== null && await startBackgroundService(opts.serviceWaitMs)) {
+ serviceStarted = true;
+ refusal = await stopAndRemove(id);
+ }
cached = null; // the next listing must show it gone (or still there, if rm refused)
- return removed ? { ok: true } : { ok: false, error: 'failed' };
+ if (refusal !== null) return { ok: false, error: 'failed', detail: refusal };
+ return serviceStarted ? { ok: true, serviceStarted } : { ok: true };
+}
+
+/** `claude stop` then `claude rm`; null once the session is gone, else what `rm` answered. */
+async function stopAndRemove(id: string): Promise {
+ await runClaude(['stop', id], CLOSE_TIMEOUT_MS).catch(() => ''); // already exited → nothing to stop
+ return runClaude(['rm', id], CLOSE_TIMEOUT_MS).then(
+ () => null,
+ // Listed a moment ago and gone now: that is the outcome asked for.
+ (e: Error) => (/no job matching/i.test(e.message) ? null : e.message),
+ );
+}
+
+/**
+ * Start Claude Code's background service as Claude Code does on demand, and wait until `claude
+ * daemon status` sees it (about a second; a few more on a busy PC). The transient service ends
+ * itself 50 s after its last client, so nothing is left running for DevDeck to clean up. False
+ * when it did not come up in time — the close is then reported as refused, with the reason.
+ */
+async function startBackgroundService(waitMs = SERVICE_WAIT_MS): Promise {
+ const how = cliInvocation(resolveAgentCliPath('claude'), ['daemon', 'run', '--origin', 'transient']);
+ try {
+ const child = spawn(how.file, how.args, { cwd: homedir(), env: claudeEnv(), detached: true, stdio: 'ignore', windowsHide: true, windowsVerbatimArguments: how.windowsVerbatimArguments });
+ child.on('error', () => undefined);
+ child.unref();
+ } catch {
+ return false;
+ }
+ const deadline = Date.now() + waitMs;
+ for (;;) {
+ if (await runClaude(['daemon', 'status'], SERVICE_STATUS_TIMEOUT_MS).then(() => true, () => false)) return true;
+ const left = deadline - Date.now();
+ if (left <= 0) return false;
+ await new Promise((r) => setTimeout(r, Math.min(SERVICE_POLL_MS, left)));
+ }
}
const CLOSE_TIMEOUT_MS = 20_000;
+const SERVICE_WAIT_MS = 10_000;
+const SERVICE_POLL_MS = 500;
+const SERVICE_STATUS_TIMEOUT_MS = 5_000;
+
+/** This process's environment without Claude Code's own session variables. */
+function claudeEnv(): NodeJS.ProcessEnv {
+ const env = { ...process.env };
+ // A DevDeck started from inside Claude Code must not make this look like a nested session.
+ for (const k of Object.keys(env)) if (/^CLAUDE/i.test(k)) delete env[k];
+ return env;
+}
-/** Run `claude ` detached from this session's own Claude environment; resolves to stdout on exit 0. */
+/**
+ * Run `claude ` detached from this session's own Claude environment; resolves to stdout on
+ * exit 0. On any other exit the error carries what the command said on stderr — that is where
+ * `rm` explains a refusal, and the diagnostics log is where it has to end up.
+ */
function runClaude(args: string[], timeoutMs: number, started?: (child: ChildProcess) => void): Promise {
return new Promise((resolve, reject) => {
- const env = { ...process.env };
- // A DevDeck started from inside Claude Code must not make this look like a nested session.
- for (const k of Object.keys(env)) if (/^CLAUDE/i.test(k)) delete env[k];
const how = cliInvocation(resolveAgentCliPath('claude'), args);
- const child = spawn(how.file, how.args, { cwd: homedir(), env, windowsHide: true, windowsVerbatimArguments: how.windowsVerbatimArguments, stdio: ['ignore', 'pipe', 'ignore'] });
+ const child = spawn(how.file, how.args, { cwd: homedir(), env: claudeEnv(), windowsHide: true, windowsVerbatimArguments: how.windowsVerbatimArguments, stdio: ['ignore', 'pipe', 'pipe'] });
let out = '';
+ let err = '';
const timer = setTimeout(() => { killChildTree(child); reject(new Error(`claude ${args[0]} timed out`)); }, timeoutMs);
started?.(child);
child.on('close', () => { if (current === child) current = null; });
- child.stdout.on('data', (b: Buffer) => { if (out.length < 2_000_000) out += b.toString('utf8'); });
+ child.stdout?.on('data', (b: Buffer) => { if (out.length < 2_000_000) out += b.toString('utf8'); });
+ child.stderr?.on('data', (b: Buffer) => { if (err.length < 4_000) err += b.toString('utf8'); });
child.on('error', (e) => { clearTimeout(timer); reject(e); });
- child.on('close', (code) => { clearTimeout(timer); if (code === 0) resolve(out); else reject(new Error(`claude ${args[0]} exited ${code}`)); });
+ child.on('close', (code) => {
+ clearTimeout(timer);
+ if (code === 0) resolve(out);
+ else reject(new Error(`claude ${args[0]} exited ${code}${err.trim() ? `: ${err.trim().replace(/\s+/g, ' ').slice(0, 300)}` : ''}`));
+ });
});
}
diff --git a/src/renderer/cockpitView.ts b/src/renderer/cockpitView.ts
index aac3fad..df6d983 100644
--- a/src/renderer/cockpitView.ts
+++ b/src/renderer/cockpitView.ts
@@ -668,8 +668,11 @@ async function closeBackgroundAgents(ids: readonly string[], ask: boolean): Prom
toast(tr('cockpit.bg_closed', { name: nameOf(agent) }));
} else if (result.error === 'host-too-old') {
toast(tr('cockpit.bg_close_old_host', { machine: machineName(machineId) }));
- } else {
+ } else if (machineId === LOCAL_MACHINE_ID) {
toast(tr('cockpit.bg_close_failed', { name: nameOf(agent), id: agent.id }));
+ } else {
+ // The command has to run on the PC that has the session — the one named here, not this one.
+ toast(tr('cockpit.bg_close_failed_on_machine', { name: nameOf(agent), id: agent.id, machine: machineName(machineId) }));
}
}
publishCockpitNavigation();
diff --git a/src/renderer/locales/en.json b/src/renderer/locales/en.json
index 1cdd133..a0fdaa7 100644
--- a/src/renderer/locales/en.json
+++ b/src/renderer/locales/en.json
@@ -646,7 +646,8 @@
"cockpit.bg_state_failed": "failed",
"cockpit.bg_close_confirm": "Close the background session '{name}'? It is removed from Claude Code's list. Its conversation is kept and can be reopened later.",
"cockpit.bg_closed": "Closed the background session '{name}' — its conversation is kept",
- "cockpit.bg_close_failed": "Could not close the background session '{name}' — try claude rm {id} in a terminal",
+ "cockpit.bg_close_failed": "Could not close the background session '{name}' — open claude agents once in a terminal on this PC (it starts Claude Code's background service), then try again, or run claude rm {id} there",
+ "cockpit.bg_close_failed_on_machine": "Could not close the background session '{name}' on {machine} — open claude agents once in a terminal on {machine} (it starts Claude Code's background service), then try again, or run claude rm {id} there",
"cockpit.bg_close_old_host": "DevDeck on {machine} is too old to close a background session — update it there",
"whatsnew.f.bg-close.title": "Close background sessions",
"whatsnew.f.bg-close.body": "A Claude Code background session (the sidebar's Background group) can now be closed from its row's ⋯ menu or with the group's bulk close. Finished or failed sessions no longer sit in the list forever. The conversation is kept.",
diff --git a/src/renderer/locales/ja.json b/src/renderer/locales/ja.json
index 0cba735..5c74546 100644
--- a/src/renderer/locales/ja.json
+++ b/src/renderer/locales/ja.json
@@ -646,7 +646,8 @@
"cockpit.bg_state_failed": "失敗",
"cockpit.bg_close_confirm": "バックグラウンドセッション '{name}' を閉じますか? Claude Code の一覧から削除されます。会話の記録は残り、後で開き直せます。",
"cockpit.bg_closed": "バックグラウンドセッション '{name}' を閉じました — 会話の記録は残っています",
- "cockpit.bg_close_failed": "バックグラウンドセッション '{name}' を閉じられませんでした — ターミナルで claude rm {id} を試してください",
+ "cockpit.bg_close_failed": "バックグラウンドセッション '{name}' を閉じられませんでした — この PC のターミナルで claude agents を一度開いてから(Claude Code のバックグラウンドサービスが起動します)もう一度試すか、そこで claude rm {id} を実行してください",
+ "cockpit.bg_close_failed_on_machine": "{machine} のバックグラウンドセッション '{name}' を閉じられませんでした — {machine} のターミナルで claude agents を一度開いてから(Claude Code のバックグラウンドサービスが起動します)もう一度試すか、そこで claude rm {id} を実行してください",
"cockpit.bg_close_old_host": "{machine} の DevDeck が古いためバックグラウンドセッションを閉じられません — そちらを更新してください",
"whatsnew.f.bg-close.title": "バックグラウンドセッションを閉じる",
"whatsnew.f.bg-close.body": "Claude Code のバックグラウンドセッション(サイドバーの「バックグラウンド」グループ)を、行の ⋯ メニューやグループの一括クローズで閉じられます。終了・失敗したセッションが一覧に残り続ける問題が解消されます。会話の記録は残ります。",
diff --git a/src/renderer/locales/ko.json b/src/renderer/locales/ko.json
index 493b8ed..2093bcf 100644
--- a/src/renderer/locales/ko.json
+++ b/src/renderer/locales/ko.json
@@ -646,7 +646,8 @@
"cockpit.bg_state_failed": "실패",
"cockpit.bg_close_confirm": "'{name}' 백그라운드 세션을 닫을까요? Claude Code 목록에서 제거됩니다. 대화 기록은 남아 있어 나중에 다시 열 수 있습니다.",
"cockpit.bg_closed": "'{name}' 백그라운드 세션을 닫았습니다 — 대화 기록은 남아 있습니다",
- "cockpit.bg_close_failed": "'{name}' 백그라운드 세션을 닫지 못했습니다 — 터미널에서 claude rm {id} 를 실행해 보세요",
+ "cockpit.bg_close_failed": "'{name}' 백그라운드 세션을 닫지 못했습니다 — 이 PC의 터미널에서 claude agents 를 한 번 연 뒤(Claude Code 백그라운드 서비스가 시작됩니다) 다시 시도하거나, 거기서 claude rm {id} 를 실행해 보세요",
+ "cockpit.bg_close_failed_on_machine": "{machine}의 '{name}' 백그라운드 세션을 닫지 못했습니다 — {machine}의 터미널에서 claude agents 를 한 번 연 뒤(Claude Code 백그라운드 서비스가 시작됩니다) 다시 시도하거나, 거기서 claude rm {id} 를 실행해 보세요",
"cockpit.bg_close_old_host": "{machine}의 DevDeck이 오래되어 백그라운드 세션을 닫을 수 없습니다 — 그쪽을 업데이트해 주세요",
"whatsnew.f.bg-close.title": "백그라운드 세션 닫기",
"whatsnew.f.bg-close.body": "Claude Code 백그라운드 세션(사이드바 「백그라운드」 그룹)을 행의 ⋯ 메뉴나 그룹 묶음 닫기로 닫을 수 있습니다. 끝났거나 실패한 세션이 목록에 영원히 남던 문제가 해결됩니다. 대화 기록은 남습니다.",
diff --git a/src/renderer/locales/zh.json b/src/renderer/locales/zh.json
index 8ffdb64..d035a5b 100644
--- a/src/renderer/locales/zh.json
+++ b/src/renderer/locales/zh.json
@@ -646,7 +646,8 @@
"cockpit.bg_state_failed": "失败",
"cockpit.bg_close_confirm": "关闭后台会话 '{name}'?它将从 Claude Code 的列表中移除。对话记录会保留,之后仍可重新打开。",
"cockpit.bg_closed": "已关闭后台会话 '{name}' — 对话记录已保留",
- "cockpit.bg_close_failed": "无法关闭后台会话 '{name}' — 请在终端运行 claude rm {id}",
+ "cockpit.bg_close_failed": "无法关闭后台会话 '{name}' — 请在本机终端先打开一次 claude agents(这会启动 Claude Code 的后台服务),然后重试,或在那里运行 claude rm {id}",
+ "cockpit.bg_close_failed_on_machine": "无法关闭 {machine} 上的后台会话 '{name}' — 请在 {machine} 的终端先打开一次 claude agents(这会启动 Claude Code 的后台服务),然后重试,或在那里运行 claude rm {id}",
"cockpit.bg_close_old_host": "{machine} 上的 DevDeck 版本过旧,无法关闭后台会话 — 请在那台电脑上更新",
"whatsnew.f.bg-close.title": "关闭后台会话",
"whatsnew.f.bg-close.body": "Claude Code 的后台会话(侧栏「后台」分组)现在可以通过行的 ⋯ 菜单或分组批量关闭来关闭。已结束或失败的会话不再永远留在列表中。对话记录会保留。",