farion1231/cc-switch · error · anyhow::Error

Skill 存储目录不能与 {app:?} 的 Skills 目录相同: {}

Error message

Skill 存储目录不能与 {app:?} 的 Skills 目录相同: {}

What it means

Chinese-language guard in CC Switch's Rust skill service: ensure_distinct_skill_roots (src-tauri/src/services/skill.rs:667-675) rejects a configuration where the SSOT skill storage directory is the same path as the target app's Skills directory. 'Same' means literal equality or both paths canonicalizing to the same real path (paths_alias, line 631-640), which catches symlinks. The guard protects sync_to_app_dir from copying a directory into itself and two apps from deleting the same entries through aliased roots. Rendered message: 'Skill 存储目录不能与 {app} 的 Skills 目录相同: {ssot_dir}' (Skill storage directory must not equal the app's Skills directory).

Source

Thrown at src-tauri/src/services/skill.rs:669

            if overlaps(&left, &right) {
                return true;
            }
        }

        // canonicalize() follows the final component and therefore fails for a
        // dangling symlink. Resolve the parents separately so two applications
        // cannot delete the same directory entry through aliased roots.
        let canonical_entry =
            |path: &Path| Some(path.parent()?.canonicalize().ok()?.join(path.file_name()?));
        matches!(
            (canonical_entry(left), canonical_entry(right)),
            (Some(left), Some(right)) if overlaps(&left, &right)
        )
    }

    fn ensure_distinct_skill_roots(ssot_dir: &Path, app_dir: &Path, app: &AppType) -> Result<()> {
        if Self::paths_alias(ssot_dir, app_dir) {
            return Err(anyhow!(
                "Skill 存储目录不能与 {app:?} 的 Skills 目录相同: {}",
                ssot_dir.display()
            ));
        }
        Ok(())
    }

    fn get_distinct_app_skills_dir(ssot_dir: &Path, app: &AppType) -> Result<PathBuf> {
        let app_dir = Self::get_app_skills_dir(app)?;
        Self::ensure_distinct_skill_roots(ssot_dir, &app_dir, app)?;
        Ok(app_dir)
    }

    fn validate_skill_storage_destination(ssot_dir: &Path) -> Result<()> {
        for app in AppType::all() {
            if matches!(app, AppType::ClaudeDesktop) {
                continue;
            }

View on GitHub (pinned to a2e22f3302)

Solutions

  1. Change one side: pick a different skill storage location, or point the app override dir somewhere other than the SSOT root (e.g. keep Claude at ~/.claude instead of ~/.agents)
  2. Remove symlinks between the SSOT directory and any app skills directory so canonical paths differ
  3. Audit settings.json override keys (claude/codex/gemini/grok/opencode/openclaw/hermes) and the Pi agent dir after the error names the colliding app
  4. Keep CC Switch's managed layout: SSOT under ~/.cc-switch/skills (or ~/.agents/skills) and let sync project into each app's own skills dir

Example fix

// before (settings.json — app skills dir aliases the unified SSOT dir)
{ "claudeOverrideDir": "~/.agents" }
// -> ~/.agents/skills == SSOT ~/.agents/skills -> error

// after
{ "claudeOverrideDir": "~/.claude" }
Defensive patterns

Strategy: try-catch

Try / catch

match SkillService::get_distinct_app_skills_dir(&ssot_dir, &app) {
    Ok(dir) => { /* proceed with sync */ }
    Err(e) if e.to_string().contains("Skill 存储目录不能与") => {
        // storage location collides with the app's skills dir: fix settings, do not retry blindly
        prompt_user_to_change_storage_location_or_override_dir();
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: SkillStorageLocation::Unified resolves to ~/.agents/skills while an app override dir (e.g. claude_override_dir) is set to ~/.agents, making the app's skills dir identical to the SSOT dir; or a symlink from ~/.claude/skills pointing at ~/.cc-switch/skills; also hit from validate_skill_storage_destination which loops over all AppTypes when changing storage location.

Common situations: Users try to 'share' one folder between CC Switch and Claude/Codex to save disk; Pi agent dir configured to ~/.agents; migrating storage location while overrides from earlier experiments are still set in settings.json; symlink-based dotfile management.

Related errors


AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16). Data as JSON: /api/errors/002346839a3b760e. Report an issue: GitHub.