google-gemini/gemini-cli · info

Skill linking cancelled by user.

Error message

Skill linking cancelled by user.

What it means

Thrown when the consent callback (`requestConsent`) returns false, signalling the user declined the prompt that lists which skills will be linked into the target directory. This is intentional user-driven cancellation, not a system failure — the installer treats 'no consent' as a stop condition and surfaces it as a thrown error so callers know nothing was written.

Source

Thrown at packages/cli/src/utils/skillUtils.ts:250

  const seenNames = new Map<string, string>();
  for (const skill of skills) {
    if (seenNames.has(skill.name)) {
      throw new Error(
        `Duplicate skill name "${skill.name}" found at multiple locations:\n  - ${seenNames.get(skill.name)}\n  - ${skill.location}`,
      );
    }
    seenNames.set(skill.name, skill.location);
  }

  const workspaceDir = process.cwd();
  const storage = new Storage(workspaceDir);
  const targetDir =
    scope === 'workspace'
      ? storage.getProjectSkillsDir()
      : Storage.getUserSkillsDir();

  if (!(await requestConsent(skills, targetDir))) {
    throw new Error('Skill linking cancelled by user.');
  }

  const resolvedTarget = path.resolve(targetDir);
  await fs.mkdir(resolvedTarget, { recursive: true });

  const linkedSkills: Array<{ name: string; location: string }> = [];

  for (const skill of skills) {
    const skillName = skill.name;
    const skillSourceDir = path.dirname(skill.location);
    const destPath = path.resolve(resolvedTarget, skillName);

    const relative = path.relative(resolvedTarget, destPath);
    if (isInvalidSubpath(relative)) {
      throw new Error('Invalid skill name: Path traversal detected.');
    }

    const exists = await fs.lstat(destPath).catch(() => null);

View on GitHub (pinned to 5024443c72)

Solutions

  1. Re-run the command and confirm at the prompt if you actually want to install the skills.
  2. Pass a `--yes` / `--force` equivalent flag or supply a consent callback that returns `true` for non-interactive runs.
  3. If the decline was policy-driven, review the skill list, whitelist the trusted source, and retry.
  4. Catch this specific error and treat it as a soft 'cancelled' status rather than a fatal failure in your orchestration code.

Example fix

// before
const consent = (skills, dir) => Promise.resolve(false);
await linkSkills(source, scope, onLog, consent); // throws

// after
const consent = (skills, dir) => Promise.resolve(true);
await linkSkills(source, scope, onLog, consent);
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await linkSkills(source, scope, onLog, consent);
} catch (e) {
  if (e instanceof Error && e.message === 'Skill linking cancelled by user.') {
    // treat as soft cancel — exit code 0 or a 'cancelled' UI state
    process.exitCode = 0;
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: The default consent callback was overridden by a caller that prompts the user (CLI confirmation dialog) and the user selected 'no'. Also triggered if a programmatic caller passes a consent function that returns `false` based on policy, or if the consent prompt is auto-denied in a non-interactive environment.

Common situations: Running the link command in a CI/automation context where the prompt defaults to decline; a user reviewing the skill list and choosing not to trust one of them; a wrapper script that auto-answers prompts negatively unless a `--yes` flag is passed.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/e4600694923e0db9. Report an issue: GitHub.