github/spec-kit · error · ValidationError

Preserved extension config for '{manifest.id}' is a symlink

Error message

Preserved extension config for '{manifest.id}' is a symlink ({cfg_file.name}) in {dest_dir}, which cannot be safely rescued during reinstall. Resolve manually — replace the symlink with a regular file or remove it — then reinstall.

What it means

Error "Preserved extension config for '{manifest.id}' is a symlink ({cfg_file.name}) in {dest_dir}, which cannot be safely rescued during reinstall. Resolve manually — replace the symlink with a regular file or remove it — then reinstall." thrown in github/spec-kit.

Source

Thrown at src/specify_cli/extensions/__init__.py:2371

            and not self.registry.is_installed(manifest.id)
            and (
                self._has_keep_config_marker(dest_dir)
                or self._is_legacy_keep_config_leftover(dest_dir)
            )
        ):
            for cfg_file in (
                list(dest_dir.glob("*-config.yml"))
                + list(dest_dir.glob("*-config.local.yml"))
            ):
                if cfg_file.is_symlink():
                    # `remove --keep-config` preserves a symlinked config
                    # because Path.is_file() follows symlinks. Its bytes cannot
                    # be safely rescued (the target may live outside dest_dir),
                    # and the rmtree below would delete the link and silently
                    # discard the kept configuration. Reject the reinstall while
                    # dest_dir is untouched so the user resolves it rather than
                    # losing the linked config.
                    raise ValidationError(
                        "Preserved extension config for "
                        f"'{manifest.id}' is a symlink ({cfg_file.name}) in "
                        f"{dest_dir}, which cannot be safely rescued during "
                        "reinstall. Resolve manually — replace the symlink with "
                        "a regular file or remove it — then reinstall."
                    )
                if cfg_file.is_file():
                    # A kept config that cannot be read or stat'ed must not
                    # crash the reinstall with a raw OSError — and must not
                    # reach the rmtree below unrescued. Like the symlink
                    # guard above, reject while dest_dir is untouched so the
                    # preserved bytes are never lost.
                    try:
                        stranded_configs[cfg_file.name] = (
                            cfg_file.read_bytes(),
                            cfg_file.stat().st_mode,
                        )
                    except OSError as exc:

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Replace the symlinked config file with a regular file (or remove it), then reinstall the extension.

When it happens

Trigger: Thrown at src/specify_cli/extensions/__init__.py:2371 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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