fix: apply the review items for #680 at merge

Restore settings.json (caseOrder, lastUsedCase) when linked-cases.json cannot be written, with the rollback test extended
List Claude Code's per-project ~/.claude.json entry as not migrated in Core-Concepts.md and CLAUDE.md
Say in the 500 that the folder is now at the new path when moving it back fails
Describe the rename dialog's folder checkbox in Core-Concepts.md, carry stoppedSessions on errors, count 5 case SSE events

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-24 14:01:33 +02:00
1 parent 1cad4dd848
commit ac6d4aa2db
6 files changed
+77 -15

No files matched your search

+26 -6
View File
@@ -1845,11 +1845,19 @@ export function registerCaseRoutes(
);
}
// Sessions really ended so far: an error after `stopSessions` still says how many are gone.
let stopped = 0;
try {
await withCaseLock(oldPath, newPath, async () => {
for (const s of open) await ctx.cleanupSession(s.id, true, 'case_rename');
for (const s of open) {
await ctx.cleanupSession(s.id, true, 'case_rename');
stopped++;
}
if (moveFolder) await moveCaseFolder(oldPath, newPath);
const originalLinks = isLinked ? await readLinkedCases() : undefined;
// The settings.json content before this rename wrote it, set only once a write is attempted (an
// absent file reads as {}, which must not be written back as a file that never existed).
let originalSettings: Record<string, unknown> | undefined;
try {
// settings.json is server-wide and caseOrder/lastUsedCase name the admin's cases, so only the
// admin's rename touches it (a non-admin's own rename must not rewrite someone else's list).
@@ -1859,6 +1867,7 @@ export function registerCaseRoutes(
const order = renameInOrder(existing.caseOrder, name, newName);
const last = existing.lastUsedCase === name ? newName : undefined;
if (order || last) {
originalSettings = existing;
await fs.writeFile(
SETTINGS_PATH,
JSON.stringify(
@@ -1881,9 +1890,20 @@ export function registerCaseRoutes(
}
}
} catch (err) {
// The folder moved but the registries did not follow: put it back rather than leave a case
// whose name and folder disagree.
if (moveFolder) await fs.rename(newPath, oldPath).catch(() => {});
// The folder moved but the registries did not follow: put settings.json and the folder back
// rather than leave a case whose name and folder disagree.
if (originalSettings) {
await fs.writeFile(SETTINGS_PATH, JSON.stringify(originalSettings, null, 2)).catch(() => {});
}
if (moveFolder) {
const movedBack = await fs.rename(newPath, oldPath).then(
() => true,
() => false
);
if (!movedBack) {
throw new Error(`${getErrorMessage(err)}. The folder could not be moved back and is now at ${newPath}.`);
}
}
throw err;
}
if (moveFolder) await moveClaudeHistory(oldPath, newPath);
@@ -1911,9 +1931,9 @@ export function registerCaseRoutes(
: err.code === 'BUSY'
? ApiErrorCode.CONFLICT
: ApiErrorCode.OPERATION_FAILED;
return fail(status, code, err.message, { renameCode: err.code });
return fail(status, code, err.message, { renameCode: err.code, stoppedSessions: stopped });
}
return fail(500, ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
return fail(500, ApiErrorCode.OPERATION_FAILED, getErrorMessage(err), { stoppedSessions: stopped });
}
});
+1 -1
View File
@@ -32,7 +32,7 @@
* - **Custom Model Endpoint Profiles** (1): swapped-out (a session's model got evicted by another session on the same llama-swap endpoint)
* - **Orchestrator** (12): stateChanged, planProgress, planReady, phase*, verification, task*, completed, error
* - **Clipboard** (1): write
* - **Cases** (4): created, linked, deleted, order-changed
* - **Cases** (5): created, linked, deleted, renamed, order-changed
* - **Docker cases** (8): exportComplete/Failed, importComplete, imageBuild*, containerRecreated
* - **Multi-user** (3): admin:usersChanged, auth:passwordChangeRequired, session:orderChanged
* - **Web tabs** (2): webview:changed, tab:layoutChanged