fix(cliproxy): add session check on delete and atomic settings writes

- Block variant deletion when CLIProxy has active sessions
- Use temp file + rename pattern for atomic settings writes
- Prevents partial writes on crash/interruption
This commit is contained in:
Tam Nhu Tran
2026-02-12 11:01:49 +07:00
parent 6ff17d8480
commit 74868972dd
2 changed files with 26 additions and 9 deletions
+18 -6
View File
@@ -15,7 +15,7 @@ import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import { DEFAULT_BACKEND } from '../platform-detector';
import { isUnifiedMode } from '../../config/unified-config-loader';
import { deleteConfigForPort } from '../config-generator';
import { deleteSessionLockForPort } from '../session-tracker';
import { hasActiveSessions, deleteSessionLockForPort } from '../session-tracker';
import { warn } from '../../utils/ui';
import {
createSettingsFile,
@@ -154,6 +154,22 @@ export function createVariant(
*/
export function removeVariant(name: string): VariantOperationResult {
try {
// First check if variant exists and has active sessions
const variants = listVariantsFromConfig();
const existingVariant = variants[name];
if (!existingVariant) {
return { success: false, error: `Variant '${name}' not found` };
}
// Check for active sessions on this variant's port before deletion
if (existingVariant.port && hasActiveSessions(existingVariant.port)) {
return {
success: false,
error: `Cannot delete variant '${name}': CLIProxy is running with active sessions. Stop the session first.`,
};
}
let variant: VariantConfig | null;
if (isUnifiedMode()) {
@@ -179,11 +195,7 @@ export function removeVariant(name: string): VariantOperationResult {
}
}
if (!variant) {
return { success: false, error: `Variant '${name}' not found` };
}
return { success: true, variant };
return { success: true, variant: variant ?? undefined };
} catch (error) {
return { success: false, error: (error as Error).message };
}
+8 -3
View File
@@ -63,10 +63,13 @@ function ensureDir(dir: string): void {
}
/**
* Write settings file atomically
* Write settings file atomically using temp file + rename.
* The renameSync is atomic on POSIX systems, preventing partial writes on crash.
*/
function writeSettings(filePath: string, settings: SettingsFile): void {
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
const tempPath = `${filePath}.tmp.${process.pid}`;
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
fs.renameSync(tempPath, filePath);
}
/**
@@ -248,7 +251,9 @@ export function updateSettingsModel(settingsPath: string, model: string): void {
delete (settings.env as unknown as Record<string, string>).ANTHROPIC_MODEL;
}
fs.writeFileSync(resolvedPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
const tempPath = `${resolvedPath}.tmp.${process.pid}`;
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
fs.renameSync(tempPath, resolvedPath);
} catch {
// Ignore errors - settings file may be invalid
}