From 22a9d58d5cec754cc5eb7defa58b070b4dc5fa39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 25 Aug 2026 07:21:11 +0000 Subject: [PATCH] a deleted commander stays deleted, and the header shows its name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit two things that should have been settled already. commanders/46 came back as an empty directory after a clean delete. not a surviving loop — nothing from 46 was running. resolveChatCwd did `mkdirSync(dir, {recursive:true})` on every commander turn, so one in-flight turn from the conversation that outlived the row recreated it. an empty directory is worse than none: a commander with no CLAUDE.md that knows nothing about itself, showing up in the file browser as something that still exists. the directory is created by POST /command-hq/commanders before any turn spawns, so a missing one means deleted. no mkdir here — fall back to the home and say so, the same degradation the missing-row branch already reports. and the detail header showed the harness's own summary title, which it makes from the first prompt: "You have been named "Voice Memos". Look in `/home/…`", on a commander whose name was correct everywhere else. the list route already prefers row.name. this now does too. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- src/servers/api/chat/websocket.ts | 23 ++++++++++++++++--- .../src/apps/CommandHQ/CommandHqDetail.tsx | 9 +++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/servers/api/chat/websocket.ts b/src/servers/api/chat/websocket.ts index 41b80af2..a0f2a602 100644 --- a/src/servers/api/chat/websocket.ts +++ b/src/servers/api/chat/websocket.ts @@ -39,7 +39,7 @@ import { import { resolveHomeDir } from '@@/user-home'; import { renameClaudeSession, type ChatIdentity } from './claude-sessions'; import { claudeLoginState } from '@@/os-user-claude'; -import { mkdirSync } from 'node:fs'; +import { mkdirSync, existsSync } from 'node:fs'; import { logger } from './logger'; import { isSuperAdmin } from '@@/super-admin'; import { snapshotClaudeUsage } from './usage-store'; @@ -201,8 +201,25 @@ async function resolveChatCwd( `[command-hq] no commander row for ${msg.commanderId !== undefined ? `id ${msg.commanderId}` : `session ${msg.resumeSessionId ?? '(none given)'}`} — falling back to the account home`, ); } - const dir = row ? getCommanderDir(email, row.dirName) : home; - mkdirSync(dir, { recursive: true }); + if (!row) return home; + + /* + The directory must already exist. `POST /command-hq/commanders` creates it with its `CLAUDE.md` + before any turn spawns, so a missing one means the commander was DELETED — and a late turn from the + conversation that outlived it would otherwise recreate it here. + + That happened: an empty `commanders/46` reappeared after a clean delete, from one in-flight turn + calling this. An empty directory is worse than none, because it is a commander with no CLAUDE.md + that knows nothing about itself, and it shows up in the file browser as something that still exists. + + So: no mkdir. Fall back to the home, and say so — the same degradation the missing-row branch above + reports, for the same reason. + */ + const dir = getCommanderDir(email, row.dirName); + if (!existsSync(dir)) { + console.warn(`[command-hq] ${row.dirName}: directory is gone — deleted mid-turn? Falling back to the home.`); + return home; + } return dir; } if (msg.context === 'email') return resolveEmailCwd(email, msg.contextId); diff --git a/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx b/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx index 8ebd019f..6dd139ed 100644 --- a/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx +++ b/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx @@ -676,7 +676,14 @@ function ResumedCommander({ sessionId, resume }: { sessionId: string; resume: Re