paperclipai/paperclip · error · Error
Refusing to overwrite ${filePath} because it changed after t
Error message
Refusing to overwrite ${filePath} because it changed after the invalid backup was created What it means
Thrown by writeConfig() inside the invalid-config recovery path: the caller DID supply options.invalidBackupPath, but the current file on disk no longer matches the backup's bytes (fs.readFileSync(filePath).equals(fs.readFileSync(invalidBackupPath)) is false), or the backup file itself is missing. This guards against a TOCTOU race where the config changed between the backup being made and the overwrite, which could cause silent data loss.
Source
Thrown at cli/src/config/store.ts:214
if (fs.existsSync(filePath)) {
try {
const source = paperclipConfigSchema.parse(migrateLegacyConfig(parseJson(filePath)));
nextConfig = paperclipConfigSchema.parse(mergePaperclipConfig(source, nextConfig));
if (isDeepStrictEqual(effectiveConfig(source), effectiveConfig(nextConfig))) {
return false;
}
} catch (error) {
const invalidBackupPath = options.invalidBackupPath;
if (!invalidBackupPath) {
throw new Error(
`Refusing to overwrite invalid config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
);
}
if (
!fs.existsSync(invalidBackupPath) ||
!fs.readFileSync(filePath).equals(fs.readFileSync(invalidBackupPath))
) {
throw new Error(
`Refusing to overwrite ${filePath} because it changed after the invalid backup was created`,
);
}
}
}
// Backup existing config before overwriting
if (fs.existsSync(filePath)) {
const backupPath = filePath + ".backup";
durableCopyFile(filePath, backupPath);
}
atomicWriteFile(filePath, JSON.stringify(nextConfig, null, 2) + "\n");
return true;
}
export function configExists(configPath?: string): boolean {
return fs.existsSync(resolveConfigPath(configPath));View on GitHub (pinned to 67001ec6eb)
Solutions
- Re-run the backup step immediately before writeConfig() so the backup reflects the current bytes, then retry.
- Ensure no other process or editor is modifying config.json concurrently (close editors, serialize CLI invocations).
- Verify invalidBackupPath exists and contains the bytes you expect before calling writeConfig().
- If the current config is now valid (someone fixed it), drop the invalid-backup flow and call writeConfig() normally.
Example fix
// before: backup taken earlier, file changed since
const backup = backupInvalidConfig(path);
// ... time passes, file is edited ...
writeConfig(cfg, path, { invalidBackupPath: backup }); // throws
// after: take backup immediately before write
import { backupInvalidConfig, writeConfig } from "./config/store.js";
const backup = backupInvalidConfig(path);
writeConfig(cfg, path, { invalidBackupPath: backup }); // no gap Defensive patterns
Strategy: validation
Validate before calling
import fs from "node:fs";
function backupMatchesCurrent(currentPath: string, backupPath: string): boolean {
if (!fs.existsSync(backupPath) || !fs.existsSync(currentPath)) return false;
return fs.readFileSync(currentPath).equals(fs.readFileSync(backupPath));
}
// Re-take backup immediately before writeConfig if mismatched Type guard
import fs from "node:fs";
function isBackupFresh(currentPath: string, backupPath: string): boolean {
try {
return fs.readFileSync(currentPath).equals(fs.readFileSync(backupPath));
} catch { return false; }
} Try / catch
try {
writeConfig(cfg, path, { invalidBackupPath: backup });
} catch (err) {
if (err instanceof Error && err.message.includes("changed after the invalid backup was created")) {
// re-take backup now and retry once
const fresh = backupInvalidConfig(path);
writeConfig(cfg, path, { invalidBackupPath: fresh });
} else throw err;
} Prevention
- Take the invalid backup immediately before writeConfig with no intervening operations.
- Serialize config writes to avoid concurrent modifications.
- Close auto-saving editors on config.json before running CLI write operations.
- Verify backup byte-equality yourself before calling writeConfig when in doubt.
When it happens
Trigger: Sequence: (1) backupInvalidConfig() copied the bad config to a .invalid-N file. (2) Something — another process, manual edit, another CLI invocation — modified the original config.json. (3) writeConfig(config, path, { invalidBackupPath }) is called; the byte-equality check fails. Also fires if invalidBackupPath was deleted between steps.
Common situations: 1) Two concurrent CLI processes both writing config. 2) User manually edited config.json after the automated backup was captured. 3) The backup path points at a stale or wrong file. 4) An editor with auto-save touched the file.
Related errors
- Refusing to overwrite invalid config at ${filePath}: ${error
- Failed to parse JSON at ${filePath}: ${err instanceof Error
- Company ID is required. Pass --company-id, set PAPERCLIP_COM
- Company ID is required. Pass --company-id, set PAPERCLIP_COM
- Company ID is required. Pass --company-id, set PAPERCLIP_COM
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/a0b4b387ee5eda98.
Report an issue: GitHub.