paperclipai/paperclip · error · Error

Refusing to overwrite invalid config at ${filePath}: ${error

Error message

Refusing to overwrite invalid config at ${filePath}: ${error instanceof Error ? error.message : String(error)}

What it means

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.

Source

Thrown at cli/src/config/store.ts:206

  configPath?: string,
  options: { invalidBackupPath?: string } = {},
): boolean {
  const filePath = resolveConfigPath(configPath);
  const dir = path.dirname(filePath);
  fs.mkdirSync(dir, { recursive: true });

  let nextConfig = paperclipConfigSchema.parse(config);
  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);

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. 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.
  2. Manually fix the existing config file until readConfig() succeeds, then retry writeConfig().
  3. Delete or move the corrupt config aside, then call writeConfig() to write a fresh one.
  4. Inspect the embedded parse error in the message to identify and correct the offending field.

Example fix

// before
writeConfig(newConfig, configPath); // throws if existing config is invalid

// after
import { backupInvalidConfig, writeConfig } from "./config/store.js";
const backup = backupInvalidConfig(configPath);
writeConfig(newConfig, configPath, { invalidBackupPath: backup });
Defensive patterns

Strategy: try-catch

Validate before calling

import { readConfig, backupInvalidConfig, writeConfig, configExists } from "./config/store.js";

function safeWriteConfig(nextConfig, configPath) {
  if (!configExists(configPath)) return writeConfig(nextConfig, configPath);
  try {
    readConfig(configPath); // confirms existing config is valid
  } catch {
    const backup = backupInvalidConfig(configPath);
    return writeConfig(nextConfig, configPath, { invalidBackupPath: backup });
  }
  return writeConfig(nextConfig, configPath);
}

Type guard

import { paperclipConfigSchema } from "./schema.js";
import fs from "node:fs";

function existingConfigIsValid(filePath: string): boolean {
  if (!fs.existsSync(filePath)) return true; // nothing to merge
  try {
    paperclipConfigSchema.parse(JSON.parse(fs.readFileSync(filePath, "utf-8")));
    return true;
  } catch { return false; }
}

Try / catch

import { writeConfig, backupInvalidConfig } from "./config/store.js";

try {
  writeConfig(cfg, configPath);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Refusing to overwrite invalid config")) {
    const backup = backupInvalidConfig(configPath);
    writeConfig(cfg, configPath, { invalidBackupPath: backup });
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/2b4baec0828fc9a0. Report an issue: GitHub.