ReactiveX/rxjs · error · Error

Refusing to install over a ${state} Skill copy at ${installa

Error message

Refusing to install over a ${state} Skill copy at ${installation.targetPath}. Use update or explicit force.

What it means

Thrown by installSkill when the target skill path already contains a copy whose state is not 'absent' (stale or locally modified) and --force was not passed. It prevents silently clobbering a user's local edits on install; the message tells you to use update instead or force explicitly.

Source

Thrown at packages/migrate/src/skill-install.ts:121

export async function inspectSkillInstallation(options: SkillInstallOptions): Promise<SkillInstallationInspection> {
  const installation = await resolveInstallation(options);
  const state = await installationState(installation);
  const provenance = state === 'absent' ? undefined : await tryReadProvenance(installation.provenancePath);
  return {
    state,
    targetPath: installation.targetPath,
    provenancePath: installation.provenancePath,
    ...(provenance ? { provenance } : {}),
    canonicalIntegrity: installation.provenance,
  };
}

export async function installSkill(options: SkillInstallOptions): Promise<SkillInstallResult> {
  const installation = await resolveInstallation(options);
  const state = await installationState(installation);
  if (state === 'current') return resultFor('install', installation, state, state, false);
  if (state !== 'absent' && !options.force) {
    throw new Error(`Refusing to install over a ${state} Skill copy at ${installation.targetPath}. Use update or explicit force.`);
  }
  await replaceInstallation(installation);
  return resultFor('install', installation, state, 'current', true);
}

export async function updateSkill(options: SkillInstallOptions): Promise<SkillInstallResult> {
  const installation = await resolveInstallation(options);
  const state = await installationState(installation);
  if (state === 'current') return resultFor('update', installation, state, state, false);
  if (state === 'modified' && !options.force) {
    throw new Error(`Refusing to overwrite a locally modified Skill copy at ${installation.targetPath} without explicit force.`);
  }
  await replaceInstallation(installation);
  return resultFor('update', installation, state, 'current', true);
}

export async function removeSkill(options: SkillInstallOptions): Promise<SkillInstallResult> {
  const installation = await resolveInstallation(options);

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Run rxjs-migrate-skill update instead, which is the intended path for existing copies
  2. If local changes are disposable, rerun install with --force
  3. Diff the installed copy against the canonical one to see what would be overwritten

Example fix

# before
rxjs-migrate-skill install --harness claude
# after
rxjs-migrate-skill update --harness claude
# or, discarding local edits:
rxjs-migrate-skill install --harness claude --force
Defensive patterns

Strategy: try-catch

Validate before calling

import { stat } from 'node:fs/promises';
const exists = await stat(targetSkillPath).then(() => true).catch(() => false);

Try / catch

try { installSkill(opts); } catch (e) { if (e instanceof Error && e.message.startsWith('Refusing to install')) { await updateSkill(opts); } else throw e; }

Prevention

When it happens

Trigger: Running rxjs-migrate-skill install when a previous skill copy exists that doesn't match the canonical digest (edited by hand or from an older version), without --force.

Common situations: Re-running install after manually tweaking SKILL.md, or after upgrading @rxjs/migrate changed the canonical skill content.

Related errors


AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28). Data as JSON: /api/errors/29a935fbf98d9790. Report an issue: GitHub.