github/spec-kit · error · ValidationError

Preserved extension config conflict for

Error message

Preserved extension config conflict for '{manifest.id}': The current config(s) ({names}) in {dest_dir} differ from their rescued backup in {rescue_staging_dir}. Both copies have been preserved. The config(s) ({names}) exist only in {dest_dir} with no counterpart in the rescued backup at {rescue_staging_dir}. Reconcile {dest_dir} and {rescue_staging_dir} to the desired final state, delete {rescue_staging_dir}, then reinstall.

What it means

Error "Preserved extension config conflict for '{manifest.id}': The current config(s) ({names}) in {dest_dir} differ from their rescued backup in {rescue_staging_dir}. Both copies have been preserved. The config(s) ({names}) exist only in {dest_dir} with no counterpart in the rescued backup at {rescue_staging_dir}. Reconcile {dest_dir} and {rescue_staging_dir} to the desired final state, delete {rescue_staging_dir}, then reinstall." thrown in github/spec-kit.

Solutions

  1. Reconcile the current config with the rescued backup to the desired state, delete the rescue staging directory, then reinstall.

When it happens

Trigger: Thrown at src/specify_cli/extensions/__init__.py:2350 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/f77868b6a4f9e65e. Report an issue: GitHub.

Appendix: source

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

                    names = ", ".join(sorted(both_diverged))
                    msg_parts.append(
                        f"The current config(s) ({names}) in {dest_dir} differ"
                        f" from their rescued backup in {rescue_staging_dir}."
                        " Both copies have been preserved."
                    )
                if live_only_conflict:
                    names = ", ".join(sorted(live_only_conflict))
                    msg_parts.append(
                        f"The config(s) ({names}) exist only in {dest_dir}"
                        f" with no counterpart in the rescued backup at"
                        f" {rescue_staging_dir}."
                    )
                msg_parts.append(
                    f"Reconcile {dest_dir} and {rescue_staging_dir} to the"
                    f" desired final state, delete {rescue_staging_dir},"
                    " then reinstall."
                )
                raise ValidationError(" ".join(msg_parts))
        elif (
            dest_dir.exists()
            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

View on GitHub (pinned to bf88c9f9a8)