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
- Re-read the current content/hash, merge your changes, and retry the write.
- If you intentionally want to force-overwrite, omit expectedHash (or pass null) so the guard is skipped.
- Surface a merge/conflict prompt to the user when the hash mismatch is detected.
- 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
- Re-read and merge on hash mismatch instead of forcing.
- Pass null expectedHash only when an intentional overwrite is acceptable.
- Shorten the read-to-write window to reduce collisions.
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
- Failed to integrate concurrent SSH git history for ${input.i
- Timed out waiting for workspace restore lock at ${lockDir}
- selected_projects exceeds the hard cap of ${MAX_PAPERCLIP_PR
- Project belongs to another company or does not exist: ${proj
- root_issues exceeds the hard cap of ${MAX_PAPERCLIP_PROFILE_
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/d5e8ce78c233ef44.
Report an issue: GitHub.