Repository: spack/spack

Issue:
Spack still creates environment even if spack.yaml is invalid
### Steps to reproduce

I was using this outdated spack.yaml file (reduced for simplicity to just one spec):

```
spack:
    specs:
    - py-pkgconfig
    concretization: together
```

Creating the environment with the following command:

```
spack env create myenv spack.yaml
```

I'm correctly getting an error `==> Error:  ... Additional properties are not allowed ('concretization' was unexpected)`.
However, `myenv` is still created by spack, can be found with `spack env list`, and most importantly *cannot be removed*.

```
$ spack env rm myenv
==> Error: /home/mdorier.linux/.spack-env/myenv/spack.yaml:2: Additional properties are not allowed ('concretization' was unexpected)
```

It has to be removed manually (i.e. `rm -rf /home/mdorier.linux/.spack-env/myenv`).

### Error message

_No response_

### Information on your system

* **Spack:** 0.21.0.dev0 (d456f32685bcb4a018ac917c7673bff727791733)
* **Python:** 3.11.4
* **Platform:** linux-ubuntu23.04-aarch64
* **Concretizer:** clingo

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:

Resolving patch:
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -380,28 +380,35 @@ def env_remove(args):
     and manifests embedded in repositories should be removed manually.
     """
     read_envs = []
+    bad_envs = []
     for env_name in args.rm_env:
-        env = ev.read(env_name)
-        read_envs.append(env)
-
-    if not args.yes_to_all:
-        answer = tty.get_yes_or_no(
-            "Really remove %s %s?"
-            % (
-                string.plural(len(args.rm_env), "environment", show_n=False),
-                string.comma_and(args.rm_env),
-            ),
-            default=False,
-        )
-        if not answer:
-            tty.die("Will not remove any environments")
-
-    for env in read_envs:
-        if env.active:
-            tty.die("Environment %s can't be removed while activated." % env.name)
-
-        env.destroy()
-        tty.msg("Successfully removed environment '%s'" % env.name)
+        try:
+            env = ev.read(env_name)
+            read_envs.append(env)
+        except spack.config.ConfigFormatError:
+            bad_envs.append(env_name)
+
+        if not args.yes_to_all:
+            environments = string.plural(len(args.rm_env), "environment", show_n=False)
+            envs = string.comma_and(args.rm_env)
+            answer = tty.get_yes_or_no(f"Really remove  {environments} {envs}?", default=False)
+            if not answer:
+                tty.die("Will not remove any environments")
+
+        for env in read_envs:
+            name = env.name
+            if env.active:
+                tty.die(f"Environment {name} can't be removed while activated.")
+            env.destroy()
+            tty.msg(f"Successfully removed environment {name}")
+
+        for bad_env_name in bad_envs:
+            shutil.rmtree(
+                spack.environment.environment.environment_dir_from_name(
+                    bad_env_name, exists_ok=True
+                )
+            )
+            tty.msg(f"Successfully removed environment '{bad_env_name}'")
 
 
 #
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -330,16 +330,21 @@ def create_in_dir(
     if with_view is None and keep_relative:
         return Environment(manifest_dir)
 
-    manifest = EnvironmentManifestFile(manifest_dir)
+    try:
+        manifest = EnvironmentManifestFile(manifest_dir)
 
-    if with_view is not None:
-        manifest.set_default_view(with_view)
+        if with_view is not None:
+            manifest.set_default_view(with_view)
 
-    if not keep_relative and init_file is not None and str(init_file).endswith(manifest_name):
-        init_file = pathlib.Path(init_file)
-        manifest.absolutify_dev_paths(init_file.parent)
+        if not keep_relative and init_file is not None and str(init_file).endswith(manifest_name):
+            init_file = pathlib.Path(init_file)
+            manifest.absolutify_dev_paths(init_file.parent)
+
+        manifest.flush()
 
-    manifest.flush()
+    except spack.config.ConfigFormatError as e:
+        shutil.rmtree(manifest_dir)
+        raise e
 
     return Environment(manifest_dir)
 


Test patch:
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -991,8 +991,26 @@ def test_bad_env_yaml_format(tmpdir):
     with tmpdir.as_cwd():
         with pytest.raises(spack.config.ConfigFormatError) as e:
             env("create", "test", "./spack.yaml")
-        assert "spack.yaml:2" in str(e)
-        assert "'spacks' was unexpected" in str(e)
+            assert "'spacks' was unexpected" in str(e)
+
+    assert "test" not in env("list")
+
+
+def test_bad_env_yaml_format_remove():
+    badenv = "badenv"
+    env("create", badenv)
+    tmpdir = spack.environment.environment.environment_dir_from_name(badenv, exists_ok=True)
+    filename = os.path.join(tmpdir, "spack.yaml")
+    with open(filename, "w") as f:
+        f.write(
+            """\
+    - mpileaks
+"""
+        )
+
+    assert badenv in env("list")
+    env("remove", "-y", badenv)
+    assert badenv not in env("list")
 
 
 def test_env_loads(install_mockery, mock_fetch):