{"record":{"id":"7b25787df16c4e11","repo":"Yeachan-Heo/oh-my-codex","slug":"plugin-skill-mirror-out-of-sync-nkind-mismatch-k","errorCode":null,"errorMessage":"plugin_skill_mirror_out_of_sync\\nkind=${mismatch.kind}\\n${mismatch.skillName ? `skill=${mismatch.skillName}` : undefined}\\nmessage=${mismatch.message}\\n${mismatch.expected ? `expected=${JSON.stringify(mismatch.expected)}` : undefined}\\n${mismatch.actual ? `actual=${JSON.stringify(mismatch.actual)}` : undefined}","messagePattern":"plugin_skill_mirror_out_of_sync\\\\nkind=(.+?)\\\\n(.+?)` : undefined\\}\\\\nmessage=(.+?)\\\\n(.+?)` : undefined\\}\\\\n(.+?)` : undefined\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/catalog/skill-mirror.ts","lineNumber":147,"sourceCode":"\n  return null;\n}\n\nexport async function assertSkillMirror(\n  expectedSkillsDir: string,\n  actualSkillsDir: string,\n  expectedSkillNames: readonly string[],\n  options: DirectoryMirrorOptions = {},\n): Promise<void> {\n  const mismatch = await compareSkillMirror(\n    expectedSkillsDir,\n    actualSkillsDir,\n    expectedSkillNames,\n    options,\n  );\n  if (!mismatch) return;\n\n  throw new Error(\n    [\n      'plugin_skill_mirror_out_of_sync',\n      `kind=${mismatch.kind}`,\n      mismatch.skillName ? `skill=${mismatch.skillName}` : undefined,\n      `message=${mismatch.message}`,\n      mismatch.expected ? `expected=${JSON.stringify(mismatch.expected)}` : undefined,\n      mismatch.actual ? `actual=${JSON.stringify(mismatch.actual)}` : undefined,\n    ].filter(Boolean).join('\\n'),\n  );\n}\n","sourceCodeStart":129,"sourceCodeEnd":158,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/catalog/skill-mirror.ts#L129-L158","documentation":"Thrown by assertSkillMirror when the on-disk mirror of plugin skills does not match the expected catalog state (missing/extra/changed skill files or metadata). The message embeds machine-readable key=value lines (kind, skill, expected, actual) describing the exact mismatch. It is an internal consistency assertion raised during syncPluginMirror, meaning the mirror directory diverged from the source of truth.","triggerScenarios":"Calling the skill-mirror sync flow when the mirrored skills directory has been manually edited, partially deleted, written by an older plugin version, or when a plugin was added/removed without re-running the mirror sync.","commonSituations":"Developers hand-edit mirrored skill files, stale mirrors left behind after upgrading a plugin, interrupted sync leaving partial files, or checkout of a repo where the mirror directory is gitignored.","solutions":["Inspect the kind/skill/expected/actual fields in the message to identify which mirrored skill diverged","Delete or restore the affected mirrored skill directory and re-run the plugin mirror sync","Reinstall or re-register the plugin that owns the mismatched skill","If the mirror is stale from an upgrade, regenerate the whole mirror rather than patching individual files"],"exampleFix":"# before\nrm -rf .omx/skills/my-skill  # partial manual delete -> out of sync\n\n# after\nomx plugin sync-mirror  # re-run full sync to rebuild mirror","handlingStrategy":"validation","validationCode":"// Before sync, verify each expected skill file exists and matches expected names\nimport { readdirSync, existsSync } from 'node:fs';\nconst expected = new Set(expectedSkillNames); // from plugin manifest\nconst actual = new Set(readdirSync(actualSkillsDir).filter(f => f.endsWith('.md')));\nfor (const name of expected) if (!actual.has(name)) throw new Error(`missing mirror skill: ${name}`);\nfor (const name of actual) if (!expected.has(name)) console.warn(`extra mirror skill: ${name}`);","typeGuard":null,"tryCatchPattern":"try {\n  await syncPluginMirror(plugin);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('plugin_skill_mirror_out_of_sync')) {\n    await rm(actualSkillsDir, { recursive: true, force: true });\n    await syncPluginMirror(plugin); // rebuild from scratch\n  } else throw e;\n}","preventionTips":["Never hand-edit mirrored skill files; edit the source plugin and re-sync","Run mirror sync immediately after adding/removing plugins","Keep the mirror directory out of manual git merges or regenerate it in CI"],"tags":["plugin","sync","filesystem","assertion"],"backgroundTag":"cache-out-of-sync","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}