Hmbown/CodeWhale · error · anyhow::Error
bundle already installed; use
Error message
bundle already installed; use `{CLI_COMMAND} update` or `remove-bundle` What it means
install_bundle installs the bundle exactly once per connection. If the current receipt record already contains a bundle (record.bundle.is_some()), re-running install-bundle would duplicate or corrupt the installed plugin, so it aborts and points at the correct commands: `update` to refresh or `remove-bundle` to uninstall first.
Solutions
- Use `codewhale dsh update` to refresh the already-installed bundle.
- Or run `codewhale dsh remove-bundle` first, then `codewhale dsh install-bundle` for a clean reinstall.
- Check `codewhale dsh status` to see whether the bundle is already present before scripting installs.
Example fix
// before codewhale dsh install-bundle # second time Error: bundle already installed // after codewhale dsh remove-bundle codewhale dsh install-bundle
Defensive patterns
Strategy: validation
Validate before calling
let status = codewhale_dsh_status()?;
if status.record.as_ref().and_then(|r| r.bundle.as_ref()).is_some() {
eprintln!("bundle already installed; use update or remove-bundle");
return Ok(()); // treat as no-op in idempotent scripts
} Type guard
fn bundle_installed(status: &DshStatusReport) -> bool {
status.record.as_ref().map_or(false, |r| r.bundle.is_some())
} Try / catch
match install_bundle(&paths, &detection, runner, &availability, app) {
Err(e) if e.to_string().contains("bundle already installed") => {
eprintln!("skipping install; already present (use update to refresh)");
}
other => other?,
} Prevention
- Make install-bundle idempotent in scripts by checking status first.
- Use `update` for refreshes and option changes, not repeated installs.
- Pair every reinstall with an explicit remove-bundle.
When it happens
Trigger: Calling `codewhale dsh install-bundle` a second time without an intervening `codewhale dsh remove-bundle`; scripted install-bundle runs on every launch; retrying a partially-successful earlier install that already recorded the bundle.
Common situations: Idempotency mistake in setup scripts; user forgets the bundle is already installed; attempting to change bundle options (skin/ocean) via install-bundle instead of `update`.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- DSH is not connected; run
- DSH plugin path not available
- overlay is stale; run
- Source already has a continuation; address it with followup
- the ` ` profile carries identity only after ` install-bundle
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/2b996d510d5d2f78.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/integrations/dsh/mod.rs:787
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).
let ocean = record.ocean_enabled;
let patch_sha = bundle::write_bundle(
&paths.bundle_dir,
&codewhale_version(),
&overlay_text,
skin,
ocean,
)?;View on GitHub (pinned to 73e0f67d83)