From 2e5550128c6cd1409d562bafcb06aae53e486db1 Mon Sep 17 00:00:00 2001 From: antfleet-ops <285575208+antfleet-ops@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:23:08 +0300 Subject: [PATCH] Preserve local ACP config across interrupted writes Constraint: fix must avoid changing config schema or keychain behavior.\nRejected: direct write to CONFIG_PATH | leaves partial JSON on interruption.\nConfidence: high\nScope-risk: narrow\nDirective: keep future config persistence rename-based or stronger; do not reintroduce truncate-in-place writes.\nTested: npm run typecheck; npm run build\nNot-tested: fsync durability across OS crash. --- src/lib/config.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index 863aced..e96f518 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs"; import { homedir } from "os"; import { resolve } from "path"; import { @@ -59,7 +59,20 @@ function loadConfig(): Config { function saveConfig(config: Config): void { mkdirSync(CONFIG_DIR, { recursive: true }); - writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n"); + const tempPath = `${CONFIG_PATH}.${process.pid}.${Date.now()}.tmp`; + try { + writeFileSync(tempPath, JSON.stringify(config, null, 2) + "\n", { + mode: 0o600, + }); + renameSync(tempPath, CONFIG_PATH); + } catch (err) { + try { + rmSync(tempPath, { force: true }); + } catch { + // best effort cleanup + } + throw err; + } } function isKeychainUnavailable(err: unknown): boolean {