github/spec-kit · error · BundlerError

No project-scoped catalog source matching '{target}' was fou

Error message

No project-scoped catalog source matching '{target}' was found.

What it means

Raised by remove_source() when neither the exact id/url match nor the canonicalized-url fallback finds a matching project-scoped source. remove_source only inspects the project catalog config (not user or built-in scopes), and the canonicalization fallback is deliberately conservative so it never deletes a different source whose url happens to equal a bare id's canonicalized path.

Source

Thrown at src/specify_cli/bundler/commands_impl/catalog_config.py:228

            f"'{target}' is a built-in default source and cannot be deleted "
            "(add a same-id source to override it instead)."
        )

    catalogs = _read(project_root)
    # Prefer an exact id/url match.
    remaining = [c for c in catalogs if c.get("id") != target and c.get("url") != target]
    if len(remaining) == len(catalogs):
        # No exact match. add_source canonicalizes a local path to an absolute
        # url before storing, so fall back to a canonicalized-url match -- this
        # lets `remove ./cat.json` undo `add ./cat.json` (stored absolute).
        # Only as a *fallback*: _canonicalize_url treats a bare id as a local
        # path (empty scheme), so applying it unconditionally could also delete a
        # different source whose url equals the id's canonicalized path.
        canonical = _canonicalize_url(target)
        if canonical != target:
            remaining = [c for c in catalogs if c.get("url") != canonical]
    if len(remaining) == len(catalogs):
        raise BundlerError(
            f"No project-scoped catalog source matching '{target}' was found."
        )
    _write(project_root, remaining)
    return target

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. List the actual project sources first (catalog list / _read) and copy the exact id or stored url string to pass to remove_source.
  2. If you added with a relative path, pass the stored absolute url (or the id) rather than the relative path.
  3. For idempotent scripts, catch this BundlerError and treat 'not found' as success.
  4. Confirm the source is not user-scoped or built-in — those are not removable through this project-scoped API.

Example fix

# before
remove_source(root, "./cat.json")  # stored as absolute url; may not match

# after
sources = read_sources(root)
match = next((s for s in sources if "cat.json" in s.url), None)
if match:
    remove_source(root, match.id)
Defensive patterns

Strategy: try-catch

Validate before calling

sources = catalog_config._read(root)
targets = {c.get("id") for c in sources} | {c.get("url") for c in sources}
if target not in targets:
    # show user the available ids/urls instead of calling remove_source

Try / catch

try:
    remove_source(root, target)
except BundlerError as e:
    if "was found" in str(e) and "No project-scoped" in str(e):
        pass  # idempotent cleanup: already absent
    else:
        raise

Prevention

When it happens

Trigger: Calling remove_source with a typo'd id or url; removing a source that was never added or already removed; removing a user-scoped or built-in source via the project-scoped API; passing a relative path that canonicalizes differently from how it was stored; passing a bare id whose canonicalized local path is empty-scheme and therefore not retried.

Common situations: Idempotent cleanup scripts that assume removal always succeeds; stale docs referencing an old source id; double-running an uninstall playbook; confusion between project scope and user scope sources.

Related errors


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