stablyai/orca · error

${targetName} provider alias was replaced with an independen

Error message

${targetName} provider alias was replaced with an independent copy

What it means

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.

Source

Thrown at config/scripts/verify-skill-update-roundtrip.mjs:215

        skillPath: `skills/${controlName}/SKILL.md`,
        skillFolderHash: controlHistorical.snapshot.gitTreeSha,
        installedAt: timestamp,
        updatedAt: timestamp
      }
    }
  }
  const lockPath = path.join(stateHome, 'skills', '.skill-lock.json')
  await mkdir(path.dirname(lockPath), { recursive: true })
  await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`)

  // Why: this is the exact user-visible rail. A bare update would include
  // unrelated vendors, while this command must leave the control skill alone.
  execSkills(['update', targetName, '--global'])
  await assertCurrentCanonical(targetName)
  const targetProviderAfter = await packageDigestAt(await realpath(targetProvider))
  const targetProviderStat = await lstat(targetProvider)
  if (shape === 'symlink' && !targetProviderStat.isSymbolicLink()) {
    throw new Error(`${targetName} provider alias was replaced with an independent copy`)
  }
  if (shape === 'symlink' && targetProviderAfter !== currentSkill(targetName).packageDigest) {
    throw new Error(`${targetName} provider alias did not converge with the canonical update`)
  }
  if (
    shape === 'copy' &&
    targetProviderAfter !== targetProviderBefore &&
    targetProviderAfter !== currentSkill(targetName).packageDigest
  ) {
    throw new Error('Independent provider copy changed to an unexpected package identity')
  }
  if (shape === 'copy') {
    // Why: hosted 1.5.17 replaces copies with aliases while equivalent local runs
    // retain the copy. Both prove this input topology must remain ineligible.
    const outcome = targetProviderStat.isSymbolicLink()
      ? 'converged to an alias'
      : targetProviderAfter === targetProviderBefore
        ? 'remained a historical copy'

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Before the update, assert the provider placement is a valid symlink (lstat.isSymbolicLink && realpath resolves).
  2. File a CLI bug: a targeted update in symlink topology must not replace the alias with an independent copy.
  3. If the symlink was dangling, recreate it correctly before re-running the round-trip.
  4. Pin to a CLI version known to preserve symlink topology until the regression is fixed.
Defensive patterns

Strategy: validation

Validate before calling

// Before the update, assert the provider placement is a healthy symlink:
import { lstatSync, realpathSync } from 'node:fs'
function assertHealthySymlink(p) {
  const st = lstatSync(p)
  if (!st.isSymbolicLink()) throw new Error(`${p} is not a symlink going into the update`)
  realpathSync(p) // throws if dangling
}
assertHealthySymlink(targetProvider)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/c826d81f13097dad. Report an issue: GitHub.