github/spec-kit · error · BundlerError

Failed to remove bundle '{bundle_id}': {exc}. {detail}

Error message

Failed to remove bundle '{bundle_id}': {exc}. {detail}

What it means

An unexpected exception occurred while uninstalling a bundle. The message distinguishes how far the removal got: either some components were already removed, a removal was attempted but nothing completed (partial uninstall possible), or nothing was attempted at all (record left unchanged). This tells you whether your project may be in a partially uninstalled state.

Source

Thrown at src/specify_cli/bundler/services/installer.py:224

    except Exception as exc:  # noqa: BLE001
        if result.uninstalled:
            detail = (
                f"{len(result.uninstalled)} component(s) were already removed "
                "before this failure; the bundle record was left unchanged, "
                "so the project may be partially uninstalled."
            )
        elif remove_attempted:
            detail = (
                "No components were removed, but the failing component may "
                "have made partial changes before raising, so the project "
                "may be partially uninstalled."
            )
        else:
            detail = (
                "No components were removed and no removal was attempted; "
                "the bundle record was left unchanged."
            )
        raise BundlerError(
            f"Failed to remove bundle '{bundle_id}': {exc}. {detail}"
        ) from exc

    return result


def _refresh_component(
    project_root: Path,
    installer: PrimitiveInstaller,
    component: ComponentRef,
) -> None:
    """Re-apply an already-installed component to bring it up to its pinned version.

    Prefers a primitive-provided ``refresh`` hook when available; otherwise falls
    back to a re-install through the existing idempotent install path.
    """
    op = getattr(installer, "refresh", None)
    if callable(op):

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Parse the {detail} clause: 'partially uninstalled' means you must inspect and finish cleanup by hand; 'record was left unchanged' means a plain retry is safe.
  2. Fix the underlying cause named in {exc} (permissions, missing file, locked file) and re-run `specify bundle remove`.
  3. After a partial uninstall, remove the leftover components individually (`specify preset remove <id>`, `specify extension remove <id>`) and then re-run the bundle remove to clear the record.
  4. Verify final state with `specify bundle list` and by checking the component directories.
Defensive patterns

Strategy: try-catch

Validate before calling

from specify_cli.bundler.services.installer import load_records
from pathlib import Path

records = load_records(project_root)
target = next((r for r in records if r.bundle_id == bundle_id), None)
if target is None:
    return
for c in target.contributed_components:
    # surface likely failures before the loop mutates anything
    assert installer.is_installed(project_root, c), f"{c.kind}/{c.id} missing"

Try / catch

from specify_cli.bundler.core import BundlerError

try:
    result = remove_bundle(project_root, bundle_id, installer)
except BundlerError as exc:
    msg = str(exc)
    if "partially uninstalled" in msg:
        enter_manual_cleanup_mode()   # inspect and finish removal by hand
    elif "record was left unchanged" in msg:
        fix_root_cause_and_retry()    # safe to retry after the cause is fixed

Prevention

When it happens

Trigger: Calling remove_bundle() where installer.remove() raises for one of target.contributed_components — e.g. a preset/extension manager remove() failing on a missing or locked file. If an earlier component already succeeded, result.removed is non-empty; if the first remove fails, remove_attempted is True with empty removed; if the failure happened before the loop, neither is set.

Common situations: Uninstalling a bundle while one of its extensions/presets has files locked or already deleted by hand; permission errors under .specify or the agent config dirs; a component directory that was manually moved or renamed.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/40a1f266d4b2c2eb. Report an issue: GitHub.