{"record":{"id":"d5e8ce78c233ef44","repo":"paperclipai/paperclip","slug":"refusing-to-overwrite-path-expected-hash-exp","errorCode":null,"errorMessage":"Refusing to overwrite ${path}: expected hash ${expectedHash} but current hash is ${currentHash}","messagePattern":"Refusing to overwrite (.+?): expected hash (.+?) but current hash is (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/plugins/plugin-llm-wiki/src/wiki/core.ts","lineNumber":1762,"sourceCode":"function mergeLocalSourceRows(sources: WikiSourceRow[], entries: PluginLocalFolderEntry[]): WikiSourceRow[] {\n  const byPath = new Map(sources.map((source) => [source.rawPath, source]));\n  for (const entry of entries) {\n    if (!entry.path.endsWith(\".md\") || byPath.has(entry.path)) continue;\n    byPath.set(entry.path, {\n      rawPath: entry.path,\n      title: null,\n      sourceType: \"local_file\",\n      url: null,\n      status: \"present\",\n      createdAt: entry.modifiedAt ?? new Date(0).toISOString(),\n    });\n  }\n  return [...byPath.values()].sort((a, b) => a.rawPath.localeCompare(b.rawPath));\n}\n\nfunction assertExpectedHash(expectedHash: string | null | undefined, currentHash: string | null, path: string): void {\n  if (expectedHash && currentHash && expectedHash !== currentHash) {\n    throw new Error(`Refusing to overwrite ${path}: expected hash ${expectedHash} but current hash is ${currentHash}`);\n  }\n}\n\nasync function upsertWikiInstance(ctx: PluginContext, input: { companyId: string; wikiId: string; rootPath?: string | null }) {\n  await ctx.db.execute(\n    `INSERT INTO ${tableName(ctx.db.namespace, \"wiki_instances\")} AS wiki_instances\n       (id, company_id, wiki_id, root_folder_key, configured_root_path, schema_version, settings, managed_agent_key, managed_project_key)\n     VALUES ($1, $2, $3, $4, $5, 1, '{}'::jsonb, $6, $7)\n     ON CONFLICT (company_id, wiki_id)\n     DO UPDATE SET configured_root_path = COALESCE(EXCLUDED.configured_root_path, wiki_instances.configured_root_path),\n                   managed_agent_key = EXCLUDED.managed_agent_key,\n                   managed_project_key = EXCLUDED.managed_project_key,\n                   updated_at = now()`,\n    [\n      randomUUID(),\n      input.companyId,\n      input.wikiId,\n      WIKI_ROOT_FOLDER_KEY,","sourceCodeStart":1744,"sourceCodeEnd":1780,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/plugin-llm-wiki/src/wiki/core.ts#L1744-L1780","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nawait writePage(ctx, { companyId, path, content, expectedHash: staleHash }); // throws\n\n// after (merge path)\nconst current = await readPage(ctx, { companyId, path });\nconst merged = mergeEdits(current.content, localEdits);\nawait writePage(ctx, { companyId, path, content: merged, expectedHash: current.hash });\n// or force overwrite\nawait writePage(ctx, { companyId, path, content: merged, expectedHash: null });","handlingStrategy":"retry","validationCode":"async function writeWithMerge(ctx, input) {\n  const current = await readPage(ctx, { companyId: input.companyId, path: input.path });\n  // merge input.content against current.content as needed\n  return writePage(ctx, { ...input, expectedHash: current.hash });\n}","typeGuard":"function isHashMismatchError(err) {\n  return /Refusing to overwrite .* expected hash .* but current hash is/.test(err?.message ?? \"\");\n}","tryCatchPattern":"async function safeWritePage(ctx, input) {\n  try {\n    return await writePage(ctx, input);\n  } catch (err) {\n    if (!isHashMismatchError(err)) throw err;\n    const current = await readPage(ctx, { companyId: input.companyId, path: input.path });\n    const merged = mergeEdits(current.content, input.content);\n    return writePage(ctx, { ...input, content: merged, expectedHash: current.hash });\n  }\n}","preventionTips":["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."],"tags":["concurrency","wiki","optimistic-locking","conflict","paperclip"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}