{"record":{"id":"2b4baec0828fc9a0","repo":"paperclipai/paperclip","slug":"refusing-to-overwrite-invalid-config-at-filepath","errorCode":null,"errorMessage":"Refusing to overwrite invalid config at ${filePath}: ${error instanceof Error ? error.message : String(error)}","messagePattern":"Refusing to overwrite invalid config at (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/config/store.ts","lineNumber":206,"sourceCode":"  configPath?: string,\n  options: { invalidBackupPath?: string } = {},\n): boolean {\n  const filePath = resolveConfigPath(configPath);\n  const dir = path.dirname(filePath);\n  fs.mkdirSync(dir, { recursive: true });\n\n  let nextConfig = paperclipConfigSchema.parse(config);\n  if (fs.existsSync(filePath)) {\n    try {\n      const source = paperclipConfigSchema.parse(migrateLegacyConfig(parseJson(filePath)));\n      nextConfig = paperclipConfigSchema.parse(mergePaperclipConfig(source, nextConfig));\n      if (isDeepStrictEqual(effectiveConfig(source), effectiveConfig(nextConfig))) {\n        return false;\n      }\n    } catch (error) {\n      const invalidBackupPath = options.invalidBackupPath;\n      if (!invalidBackupPath) {\n        throw new Error(\n          `Refusing to overwrite invalid config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`,\n        );\n      }\n      if (\n        !fs.existsSync(invalidBackupPath) ||\n        !fs.readFileSync(filePath).equals(fs.readFileSync(invalidBackupPath))\n      ) {\n        throw new Error(\n          `Refusing to overwrite ${filePath} because it changed after the invalid backup was created`,\n        );\n      }\n    }\n  }\n\n  // Backup existing config before overwriting\n  if (fs.existsSync(filePath)) {\n    const backupPath = filePath + \".backup\";\n    durableCopyFile(filePath, backupPath);","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/config/store.ts#L188-L224","documentation":"Thrown by writeConfig() when an existing config file at the resolved path fails to parse/validate against paperclipConfigSchema during the merge-read step, AND the caller did not pass options.invalidBackupPath. The library refuses to silently overwrite a config it cannot understand, because doing so could destroy fields it does not recognize. The original parse error is embedded in the message.","triggerScenarios":"Called writeConfig(config, configPath?, options) where the file exists but paperclipConfigSchema.parse(migrateLegacyConfig(parseJson(filePath))) throws (corrupt JSON, schema mismatch), and options.invalidBackupPath is undefined. The catch block checks for a backup path; without one it re-throws with this refusal message.","commonSituations":"1) Existing config.json was hand-edited to an invalid state, then a programmatic writeConfig() call is made without the invalid-backup flow. 2) Version downgrade: a newer CLI wrote config fields the older schema rejects. 3) The config got corrupted by a partial write or merge tool.","solutions":["Pass options.invalidBackupPath pointing at a path returned by backupInvalidConfig() so the library can safely overwrite the bad config after verifying it matches the backup.","Manually fix the existing config file until readConfig() succeeds, then retry writeConfig().","Delete or move the corrupt config aside, then call writeConfig() to write a fresh one.","Inspect the embedded parse error in the message to identify and correct the offending field."],"exampleFix":"// before\nwriteConfig(newConfig, configPath); // throws if existing config is invalid\n\n// after\nimport { backupInvalidConfig, writeConfig } from \"./config/store.js\";\nconst backup = backupInvalidConfig(configPath);\nwriteConfig(newConfig, configPath, { invalidBackupPath: backup });","handlingStrategy":"try-catch","validationCode":"import { readConfig, backupInvalidConfig, writeConfig, configExists } from \"./config/store.js\";\n\nfunction safeWriteConfig(nextConfig, configPath) {\n  if (!configExists(configPath)) return writeConfig(nextConfig, configPath);\n  try {\n    readConfig(configPath); // confirms existing config is valid\n  } catch {\n    const backup = backupInvalidConfig(configPath);\n    return writeConfig(nextConfig, configPath, { invalidBackupPath: backup });\n  }\n  return writeConfig(nextConfig, configPath);\n}","typeGuard":"import { paperclipConfigSchema } from \"./schema.js\";\nimport fs from \"node:fs\";\n\nfunction existingConfigIsValid(filePath: string): boolean {\n  if (!fs.existsSync(filePath)) return true; // nothing to merge\n  try {\n    paperclipConfigSchema.parse(JSON.parse(fs.readFileSync(filePath, \"utf-8\")));\n    return true;\n  } catch { return false; }\n}","tryCatchPattern":"import { writeConfig, backupInvalidConfig } from \"./config/store.js\";\n\ntry {\n  writeConfig(cfg, configPath);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Refusing to overwrite invalid config\")) {\n    const backup = backupInvalidConfig(configPath);\n    writeConfig(cfg, configPath, { invalidBackupPath: backup });\n  } else throw err;\n}","preventionTips":["Always pass { invalidBackupPath } when calling writeConfig in automated flows that may encounter a corrupt existing config.","Pre-validate the existing config with readConfig() before writing to detect corruption early.","Surface the embedded parse error to the user so they can decide whether to overwrite."],"tags":["config","safety-guard","write","cli"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}