paperclipai/paperclip · warning · Error

Refusing to overwrite ${path}: expected hash ${expectedHash}

Error message

Refusing to overwrite ${path}: expected hash ${expectedHash} but current hash is ${currentHash}

What it means

Thrown by assertExpectedHash when both an expectedHash and a currentHash are present and they differ. It is an optimistic-concurrency guard for wiki page/raw writes: if the stored content changed between the caller's read and write, the write is refused to prevent a blind overwrite.

Source

Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:1762

function mergeLocalSourceRows(sources: WikiSourceRow[], entries: PluginLocalFolderEntry[]): WikiSourceRow[] {
  const byPath = new Map(sources.map((source) => [source.rawPath, source]));
  for (const entry of entries) {
    if (!entry.path.endsWith(".md") || byPath.has(entry.path)) continue;
    byPath.set(entry.path, {
      rawPath: entry.path,
      title: null,
      sourceType: "local_file",
      url: null,
      status: "present",
      createdAt: entry.modifiedAt ?? new Date(0).toISOString(),
    });
  }
  return [...byPath.values()].sort((a, b) => a.rawPath.localeCompare(b.rawPath));
}

function assertExpectedHash(expectedHash: string | null | undefined, currentHash: string | null, path: string): void {
  if (expectedHash && currentHash && expectedHash !== currentHash) {
    throw new Error(`Refusing to overwrite ${path}: expected hash ${expectedHash} but current hash is ${currentHash}`);
  }
}

async function upsertWikiInstance(ctx: PluginContext, input: { companyId: string; wikiId: string; rootPath?: string | null }) {
  await ctx.db.execute(
    `INSERT INTO ${tableName(ctx.db.namespace, "wiki_instances")} AS wiki_instances
       (id, company_id, wiki_id, root_folder_key, configured_root_path, schema_version, settings, managed_agent_key, managed_project_key)
     VALUES ($1, $2, $3, $4, $5, 1, '{}'::jsonb, $6, $7)
     ON CONFLICT (company_id, wiki_id)
     DO UPDATE SET configured_root_path = COALESCE(EXCLUDED.configured_root_path, wiki_instances.configured_root_path),
                   managed_agent_key = EXCLUDED.managed_agent_key,
                   managed_project_key = EXCLUDED.managed_project_key,
                   updated_at = now()`,
    [
      randomUUID(),
      input.companyId,
      input.wikiId,
      WIKI_ROOT_FOLDER_KEY,

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Re-read the current content/hash, merge your changes, and retry the write.
  2. If you intentionally want to force-overwrite, omit expectedHash (or pass null) so the guard is skipped.
  3. Surface a merge/conflict prompt to the user when the hash mismatch is detected.
  4. Reduce the window between read and write to lower collision probability.

Example fix

// before
await writePage(ctx, { companyId, path, content, expectedHash: staleHash }); // throws

// after (merge path)
const current = await readPage(ctx, { companyId, path });
const merged = mergeEdits(current.content, localEdits);
await writePage(ctx, { companyId, path, content: merged, expectedHash: current.hash });
// or force overwrite
await writePage(ctx, { companyId, path, content: merged, expectedHash: null });
Defensive patterns

Strategy: retry

Validate before calling

async function writeWithMerge(ctx, input) {
  const current = await readPage(ctx, { companyId: input.companyId, path: input.path });
  // merge input.content against current.content as needed
  return writePage(ctx, { ...input, expectedHash: current.hash });
}

Type guard

function isHashMismatchError(err) {
  return /Refusing to overwrite .* expected hash .* but current hash is/.test(err?.message ?? "");
}

Try / catch

async function safeWritePage(ctx, input) {
  try {
    return await writePage(ctx, input);
  } catch (err) {
    if (!isHashMismatchError(err)) throw err;
    const current = await readPage(ctx, { companyId: input.companyId, path: input.path });
    const merged = mergeEdits(current.content, input.content);
    return writePage(ctx, { ...input, content: merged, expectedHash: current.hash });
  }
}

Prevention

When it happens

Trigger: Calling a wiki write API with an expectedHash computed from an older revision of the file, while another writer has since updated it (different currentHash).

Common situations: Two editors/agents editing the same page concurrently; stale hash cached client-side; long-running edit session where the file was synced underneath; retries after a delay.

Related errors


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