{"record":{"id":"02dad5df5b4534e4","repo":"stablyai/orca","slug":"targetname-provider-alias-did-not-converge-with","errorCode":null,"errorMessage":"${targetName} provider alias did not converge with the canonical update","messagePattern":"(.+?) provider alias did not converge with the canonical update","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/scripts/verify-skill-update-roundtrip.mjs","lineNumber":218,"sourceCode":"        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'\n        : 'converged as a copy'\n    console.log(`[skill-update-roundtrip] independent copy ${outcome}`)\n  }","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/verify-skill-update-roundtrip.mjs#L200-L236","documentation":"In symlink shape, after the update, the script resolves the target provider's realpath and hashes it; the digest must equal currentSkill(targetName).packageDigest. If the alias still points somewhere but at stale content, it throws — the alias must converge to the canonical updated package.","triggerScenarios":"The update rewrote the canonical placement but left the provider symlink pointing at the old target; the symlink is relative and points at a path that wasn't updated; realpath resolves to a cached/old checkout; the canonical digest itself is stale.","commonSituations":"Symlink target path drift between CLI versions; update writes the canonical to a versioned directory but the symlink still points at the previous version directory; partial update that touched the lock but not the alias target.","solutions":["Inspect the provider symlink target (readlink) and the canonical placement digest to see which is stale.","Confirm assertCurrentCanonical passed (canonical is up to date) before diagnosing the alias — if canonical is wrong, fix 289 first.","File a CLI bug: an update that refreshes the canonical must also repoint existing symlinks at the new canonical.","Pin to a CLI version whose update path repoints aliases correctly."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-assert the symlink target will track the canonical directory:\nimport { readlinkSync } from 'node:fs'\nimport path from 'node:path'\nfunction assertAliasTracksCanonical(providerPath, canonicalPath) {\n  const target = readlinkSync(providerPath)\n  const resolved = path.resolve(path.dirname(providerPath), target)\n  if (resolved !== canonicalPath) {\n    throw new Error(`Provider alias ${providerPath} -> ${resolved} does not track canonical ${canonicalPath}`)\n  }\n}\n// If this fails before the update, the alias was already stale; fix the fixture first.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Confirm assertCurrentCanonical passed first — a stale canonical explains a stale alias.","Use absolute symlink targets in the fixture to avoid relative-resolution drift.","Pin the CLI version whose update repoints aliases at the canonical."],"tags":["skill-update","symlink","digest","stale-alias","regression-detection"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}