Hmbown/CodeWhale · error · anyhow::Error

DSH plugin path not available

Error message

DSH plugin path not available: {reason}

What it means

install_bundle checks the precomputed BundleAvailability before doing any work. When the plugin path (the pnpm-managed location where the Codewhale bundle would be added via `dsh plugin --profile codewhale add`) is not usable — pnpm missing, wrong version, unwritable path — it aborts with this error carrying the availability `reason`.

Solutions

  1. Install or enable pnpm (e.g. `corepack enable pnpm` or `npm i -g pnpm`) and confirm `pnpm --version` meets the requirement stated in the error's reason.
  2. Fix the path/permission issue named in the `{reason}` and retry `codewhale dsh install-bundle`.
  3. Verify the dsh plugin directory exists and is writable by the current user.

Example fix

// before
codewhale dsh install-bundle
Error: DSH plugin path not available: pnpm not found on PATH

// after
corepack enable pnpm
pnpm --version
codewhale dsh install-bundle
Defensive patterns

Strategy: validation

Validate before calling

if !matches!(availability, BundleAvailability::Available { .. }) {
    if let BundleAvailability::NotAvailable { reason } = availability {
        eprintln!("fix plugin path first: {reason}");
    }
    return Err(...);
}

Type guard

fn pnpm_available(a: &BundleAvailability) -> Option<&str> {
    match a { BundleAvailability::Available { pnpm_version } => Some(pnpm_version), _ => None }
}

Try / catch

match install_bundle(&paths, &detection, runner, &availability, app) {
    Err(e) if e.to_string().starts_with("DSH plugin path not available") => {
        install_pnpm()?;
        // recompute availability and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `codewhale dsh install-bundle` (or update paths that reuse install_bundle) while the earlier availability probe returned BundleAvailability::NotAvailable { reason } — e.g. pnpm is not installed or its version is below the required minimum.

Common situations: Node/pnpm toolchain not installed; pnpm installed only via corepack and not enabled; restricted CI container lacking pnpm; permissions on the dsh plugin directory.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/a4a8cf064763f64c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/integrations/dsh/mod.rs:779

    let status = command
        .status()
        .with_context(|| format!("launch {}", spec.binary.display()))?;
    Ok(status.code().unwrap_or(1))
}

/// Install the Codewhale bundle into the dedicated `codewhale` DSH profile via
/// the documented `dsh plugin --profile codewhale add <path>` (pnpm).
pub(crate) fn install_bundle(
    paths: &DshPaths,
    detection: &DshDetection,
    runner: &dyn DshRunner,
    availability: &BundleAvailability,
    app: DshAppBundle,
) -> Result<DshBundleRecord> {
    let pnpm_version = match availability {
        BundleAvailability::Available { pnpm_version } => pnpm_version.clone(),
        BundleAvailability::NotAvailable { reason } => {
            anyhow::bail!("DSH plugin path not available: {reason}")
        }
    };
    let mut doc = DshReceiptDocument::load(&paths.receipt)?;
    let Some(mut record) = doc.current.take() else {
        anyhow::bail!("DSH is not connected; run `{CLI_COMMAND} connect` first");
    };
    if record.bundle.is_some() {
        anyhow::bail!("bundle already installed; use `{CLI_COMMAND} update` or `remove-bundle`");
    }
    let overlay_text = std::fs::read_to_string(&paths.overlay)
        .with_context(|| format!("read {}", paths.overlay.display()))?;
    if sha256_hex(overlay_text.as_bytes()) != record.overlay_sha256 {
        anyhow::bail!("overlay is stale; run `{CLI_COMMAND} update` before install-bundle");
    }
    // install-bundle defaults the palette on; `update --skin false` is the
    // off switch. `connect --skin` records the same decision for a later update.
    let skin = true;
    // The ocean scene follows the recorded decision (default on).

View on GitHub (pinned to 73e0f67d83)