{"record":{"id":"c0bfc5e6b83a97b5","repo":"EveryInc/compound-engineering-plugin","slug":"skipping-targetpath-existing-unmanaged-file-n-c0bfc5","errorCode":null,"errorMessage":"Skipping ${targetPath}: existing unmanaged file (not overwritten)","messagePattern":"Skipping (.+?): existing unmanaged file \\(not overwritten\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/targets/opencode.ts","lineNumber":41,"sourceCode":"// subsequent write -- writing through a preserved symlink would clobber the\n// user's fork, which is worse than not overwriting at all). All four\n// OpenCode artifact kinds (agents, commands, plugins, skills) are tracked in\n// the install manifest's `groups` map, so the same ownership rule applies\n// uniformly here rather than a symlink-only guard.\nasync function cleanupCurrentManagedFile(\n  targetPath: string,\n  manifest: ManagedInstallManifest | null,\n  group: string,\n  entryName: string,\n): Promise<boolean> {\n  const stat = await lstatOrNull(targetPath)\n  if (!stat) return false\n  if (stat.isSymbolicLink()) {\n    console.warn(`Skipping ${targetPath}: existing user-managed symlink (not overwritten)`)\n    return true\n  }\n  if (!manifest?.groups[group]?.includes(entryName)) {\n    console.warn(`Skipping ${targetPath}: existing unmanaged file (not overwritten)`)\n    return true\n  }\n  return false\n}\n\nasync function mergeOpenCodeConfig(\n  configPath: string,\n  incoming: OpenCodeConfig,\n): Promise<OpenCodeConfig> {\n  if (!(await pathExists(configPath))) return incoming\n\n  let existing: OpenCodeConfig\n  try {\n    existing = await readJson<OpenCodeConfig>(configPath)\n  } catch {\n    console.warn(\n      `Warning: existing ${configPath} is not valid JSON. Writing plugin config without merging.`\n    )","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/EveryInc/compound-engineering-plugin/blob/c9c10f8c75412c7232cb2bd663e5fd1cea98d84e/src/targets/opencode.ts#L23-L59","documentation":"Also from cleanupCurrentManagedFile in src/targets/opencode.ts: when the target exists as a regular file (not a symlink) but is NOT listed in the install manifest group (manifest?.groups[group] does not include entryName), it is treated as user-created/unmanaged and left untouched. The converter only deletes or overwrites files it can prove it installed previously.","triggerScenarios":"Running an OpenCode install/cleanup where a file exists at a managed location (e.g. .opencode/agents/foo.md) but the recorded install manifest for that group does not list it — e.g. the file was created by hand, by another tool, or the manifest was deleted/reset.","commonSituations":"Users hand-editing or hand-adding agent/command files alongside installed ones; manifest lost after a manual ~/.config or .opencode cleanup; switching plugin versions where the old manifest recorded different names; another agent platform's installer writing into .opencode.","solutions":["If the file is yours, rename it or move it out of the managed directory so it no longer shadows the managed name.","If you want the plugin's version, delete the unmanaged file (after backing up customizations) and re-run the install.","If the manifest is stale/missing, remove the plugin's managed output and reinstall cleanly so the manifest is regenerated, then reapply customizations via config rather than editing generated files.","Check the manifest to confirm the name: the entry must appear in manifest.groups[group] to be considered managed."],"exampleFix":"// before: hand-made file blocks managed write\nls .opencode/agents/review.md  # not in manifest\n// after\nmv .opencode/agents/review.md .opencode/agents/review.local.md\nbun run convert --to opencode","handlingStrategy":"validation","validationCode":"import { existsSync, readFileSync } from 'node:fs'\nfunction isManifestManaged(manifestPath: string, group: string, entryName: string): boolean {\n  if (!existsSync(manifestPath)) return false\n  const m = JSON.parse(readFileSync(manifestPath, 'utf8'))\n  return Array.isArray(m?.groups?.[group]) && m.groups[group].includes(entryName)\n}\n// if false for an existing file, the installer will treat it as unmanaged and skip it","typeGuard":"function hasGroupEntry(m: unknown, group: string, entry: string): m is { groups: Record<string, string[]> } {\n  return typeof m === 'object' && m !== null &&\n    Array.isArray((m as any).groups?.[group]) &&\n    (m as any).groups[group].includes(entry)\n}","tryCatchPattern":null,"preventionTips":["Never hand-edit files inside managed groups; add your own files under distinct names","Keep the install manifest intact — deleting it makes all installed files 'unmanaged'","After upgrading, re-run install so the manifest reflects current names"],"tags":["opencode","install","manifest","unmanaged-file"],"backgroundTag":"unmanaged-file-not-overwritten","analyzedSha":"c9c10f8c75412c7232cb2bd663e5fd1cea98d84e","analyzedAt":"2026-08-31T15:18:07.959Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}