{"record":{"id":"c826d81f13097dad","repo":"stablyai/orca","slug":"targetname-provider-alias-was-replaced-with-an","errorCode":null,"errorMessage":"${targetName} provider alias was replaced with an independent copy","messagePattern":"(.+?) provider alias was replaced with an independent copy","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/scripts/verify-skill-update-roundtrip.mjs","lineNumber":215,"sourceCode":"        skillPath: `skills/${controlName}/SKILL.md`,\n        skillFolderHash: controlHistorical.snapshot.gitTreeSha,\n        installedAt: timestamp,\n        updatedAt: timestamp\n      }\n    }\n  }\n  const lockPath = path.join(stateHome, 'skills', '.skill-lock.json')\n  await mkdir(path.dirname(lockPath), { recursive: true })\n  await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\\n`)\n\n  // Why: this is the exact user-visible rail. A bare update would include\n  // unrelated vendors, while this command must leave the control skill alone.\n  execSkills(['update', targetName, '--global'])\n  await assertCurrentCanonical(targetName)\n  const targetProviderAfter = await packageDigestAt(await realpath(targetProvider))\n  const targetProviderStat = await lstat(targetProvider)\n  if (shape === 'symlink' && !targetProviderStat.isSymbolicLink()) {\n    throw new Error(`${targetName} provider alias was replaced with an independent copy`)\n  }\n  if (shape === 'symlink' && targetProviderAfter !== currentSkill(targetName).packageDigest) {\n    throw new Error(`${targetName} provider alias did not converge with the canonical update`)\n  }\n  if (\n    shape === 'copy' &&\n    targetProviderAfter !== targetProviderBefore &&\n    targetProviderAfter !== currentSkill(targetName).packageDigest\n  ) {\n    throw new Error('Independent provider copy changed to an unexpected package identity')\n  }\n  if (shape === 'copy') {\n    // Why: hosted 1.5.17 replaces copies with aliases while equivalent local runs\n    // retain the copy. Both prove this input topology must remain ineligible.\n    const outcome = targetProviderStat.isSymbolicLink()\n      ? 'converged to an alias'\n      : targetProviderAfter === targetProviderBefore\n        ? 'remained a historical copy'","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/verify-skill-update-roundtrip.mjs#L197-L233","documentation":"After a targeted 'skills update <targetName> --global' in symlink shape, the script lstat's the target provider placement and requires it to still be a symbolic link. If the update replaced the symlink with a real directory/copy, it throws — the topology must be preserved as an alias.","triggerScenarios":"The CLI's update path, when fixing an out-of-date provider, deletes the symlink and writes an independent directory instead; a copy-fallback path triggers when it shouldn't for symlink shape; the provider symlink was already broken before the update and the update 'repaired' it as a copy.","commonSituations":"CLI regression that changes topology from alias to copy on update; pre-existing dangling symlink that the update normalizes; cross-platform path handling that turns a relative symlink into a resolved copy.","solutions":["Before the update, assert the provider placement is a valid symlink (lstat.isSymbolicLink && realpath resolves).","File a CLI bug: a targeted update in symlink topology must not replace the alias with an independent copy.","If the symlink was dangling, recreate it correctly before re-running the round-trip.","Pin to a CLI version known to preserve symlink topology until the regression is fixed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before the update, assert the provider placement is a healthy symlink:\nimport { lstatSync, realpathSync } from 'node:fs'\nfunction assertHealthySymlink(p) {\n  const st = lstatSync(p)\n  if (!st.isSymbolicLink()) throw new Error(`${p} is not a symlink going into the update`)\n  realpathSync(p) // throws if dangling\n}\nassertHealthySymlink(targetProvider)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat any topology change after a targeted update as a CLI regression; file it rather than weakening the assertion.","On Windows, run only where symlink privilege is available when shape=symlink.","Pin the CLI version under test; don't let 'latest' introduce a topology regression silently."],"tags":["skill-update","symlink","topology","regression-detection","ci-gate"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}