Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-hatch for openSUSE:Factory 
checked in at 2026-01-29 17:46:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-hatch (Old)
 and      /work/SRC/openSUSE:Factory/.python-hatch.new.1995 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-hatch"

Thu Jan 29 17:46:57 2026 rev:24 rq:1329804 version:1.16.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-hatch/python-hatch.changes        
2026-01-07 16:01:21.954995394 +0100
+++ /work/SRC/openSUSE:Factory/.python-hatch.new.1995/python-hatch.changes      
2026-01-29 17:49:29.740930120 +0100
@@ -1,0 +2,16 @@
+Wed Jan 28 13:25:08 UTC 2026 - Markéta Machová <[email protected]>
+
+- Update to 1.16.3
+  * Env var for keep-env when an exception occurs during environment
+    creation to enable debugging.
+  * Fix issue with self-referential dependencies not being recognized.
+  * Fix incomplete environments created when an exception occurs during
+    creation.
+  * Fix dependency-groups not working with when environment is not
+    marked as builder.
+  * Change Keyring to take expect repository URL instead of repository
+    name.
+- Drop python39 build requirement
+- Add shell.patch to fix visible regression with 1.16.3
+
+-------------------------------------------------------------------

Old:
----
  hatch-v1.16.2.tar.gz

New:
----
  hatch-v1.16.3.tar.gz
  shell.patch

----------(New B)----------
  New:- Drop python39 build requirement
- Add shell.patch to fix visible regression with 1.16.3
----------(New E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-hatch.spec ++++++
--- /var/tmp/diff_new_pack.qjqw31/_old  2026-01-29 17:49:30.848977122 +0100
+++ /var/tmp/diff_new_pack.qjqw31/_new  2026-01-29 17:49:30.848977122 +0100
@@ -31,7 +31,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-hatch%{psuffix}
-Version:        1.16.2
+Version:        1.16.3
 Release:        0
 Summary:        Modern, extensible Python project management
 License:        MIT
@@ -53,6 +53,8 @@
 Source22:       
https://files.pythonhosted.org/packages/py3/s/setuptools/setuptools-80.9.0-py3-none-any.whl
 Source23:       
https://files.pythonhosted.org/packages/py3/t/trove_classifiers/trove_classifiers-2025.12.1.14-py3-none-any.whl
 Source24:       
https://files.pythonhosted.org/packages/py3/u/urllib3/urllib3-2.6.2-py3-none-any.whl
+# PATCH-FIX-UPSTREAM https://github.com/pypa/hatch/pull/2165 keep_env TypeError
+Patch0:         shell.patch
 BuildRequires:  %{python_module base >= 3.10}
 BuildRequires:  %{python_module hatch-vcs >= 0.3}
 BuildRequires:  %{python_module hatchling >= 1.27}
@@ -95,12 +97,8 @@
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module trustme}
 BuildRequires:  cargo
-%if 0%{?suse_version} >= 1699
-BuildRequires:  python39-base
 %endif
-%else
 BuildArch:      noarch
-%endif
 %python_subpackages
 
 %description
@@ -172,6 +170,10 @@
 donttest+=" or test_workspace_documentation_generation"
 donttest+=" or test_workspace_development_workflow"
 donttest+=" or test_workspace_overrides_matrix_conditional_members"
+# multiple tests fail with rich 14.3 https://github.com/pypa/hatch/issues/2170
+rm tests/cli/env/test_show.py tests/cli/python/test_show.py 
tests/cli/dep/show/test_table.py
+# flaky test
+donttest+=" or test_workspace_overrides_combined_conditions"
 %pytest -n auto -v -k "not ($donttest)"
 %endif
 






++++++ hatch-v1.16.2.tar.gz -> hatch-v1.16.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/.github/workflows/test.yml 
new/hatch-hatch-v1.16.3/.github/workflows/test.yml
--- old/hatch-hatch-v1.16.2/.github/workflows/test.yml  2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/.github/workflows/test.yml  2026-01-21 
02:32:44.000000000 +0100
@@ -24,13 +24,13 @@
       fail-fast: false
       matrix:
         os: [ubuntu-latest, windows-latest, macos-latest]
-        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
+        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
 
     steps:
     - uses: actions/checkout@v4
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: ${{ matrix.python-version }}
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/docs/history/hatch.md 
new/hatch-hatch-v1.16.3/docs/history/hatch.md
--- old/hatch-hatch-v1.16.2/docs/history/hatch.md       2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/docs/history/hatch.md       2026-01-21 
02:32:44.000000000 +0100
@@ -8,6 +8,19 @@
 
 ## Unreleased
 
+## [1.16.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.3) - 
2026-01-20 ## {: #hatch-v1.16.3 }
+
+***Added:***
+
+- Env var for keep-env when an exception occurs during environment creation to 
enable debugging.
+
+***Fixed:***
+
+- Fix issue with self-referential dependencies not being recognized. 
+- Fix incomplete environments created when an exception occurs during creation.
+- Fix dependency-groups not working with when environment is not marked as 
builder. 
+- Change Keyring to take expect repository URL instead of repository name. 
+
 ## [1.16.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.2) - 
2025-12-06 ## {: #hatch-v1.16.2 }
 
 ***Fixed:***
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hatch-hatch-v1.16.2/docs/how-to/environment/dependency-resolution.md 
new/hatch-hatch-v1.16.3/docs/how-to/environment/dependency-resolution.md
--- old/hatch-hatch-v1.16.2/docs/how-to/environment/dependency-resolution.md    
2025-12-06 19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/docs/how-to/environment/dependency-resolution.md    
2026-01-21 02:32:44.000000000 +0100
@@ -18,8 +18,8 @@
 
 ```toml config-example
 [tool.hatch.envs.default.env-vars]
-UV_EXTRA_INDEX_URL = 
"https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group1_path>/-/packages/pypi/simple/"
-UV_INDEX_URL = 
"https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group2_path>/-/packages/pypi/simple/
 https://pypi.org/simple/";
+UV_INDEX = 
"https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group1_path>/-/packages/pypi/simple/
 
https://token:{env:GITLAB_API_TOKEN}@gitlab.com/api/v4/groups/<group2_path>/-/packages/pypi/simple/"
+UV_DEFAULT_INDEX = "https://pypi.org/simple/";
 ```
 
 !!! tip
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hatch-hatch-v1.16.2/docs/plugins/build-hook/reference.md 
new/hatch-hatch-v1.16.3/docs/plugins/build-hook/reference.md
--- old/hatch-hatch-v1.16.2/docs/plugins/build-hook/reference.md        
2025-12-06 19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/docs/plugins/build-hook/reference.md        
2026-01-21 02:32:44.000000000 +0100
@@ -14,6 +14,7 @@
 - [hatch-jupyter-builder](https://github.com/jupyterlab/hatch-jupyter-builder) 
- used for packages in the Project Jupyter ecosystem
 - [hatch-mypyc](https://github.com/ofek/hatch-mypyc) - compiles code with 
[Mypyc](https://github.com/mypyc/mypyc)
 - [hatch-odoo](https://github.com/acsone/hatch-odoo) - package Odoo add-ons 
into the appropriate namespace
+- [hatch-project-name](https://github.com/valentinoli/hatch-project-name/) - 
writes the project name to a file
 - [scikit-build-core](https://github.com/scikit-build/scikit-build-core) - 
build extension modules with CMake
 
 ## Overview
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/hatch.toml 
new/hatch-hatch-v1.16.3/hatch.toml
--- old/hatch-hatch-v1.16.2/hatch.toml  2025-12-06 19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/hatch.toml  2026-01-21 02:32:44.000000000 +0100
@@ -118,6 +118,7 @@
 ]
 
 [envs.release]
+workspace.members = ["backend/"]
 detached = true
 installer = "uv"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/__init__.py 
new/hatch-hatch-v1.16.3/src/hatch/cli/__init__.py
--- old/hatch-hatch-v1.16.2/src/hatch/cli/__init__.py   2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/cli/__init__.py   2026-01-21 
02:32:44.000000000 +0100
@@ -92,7 +92,18 @@
 )
 @click.version_option(version=__version__, prog_name="Hatch")
 @click.pass_context
-def hatch(ctx: click.Context, env_name, project, verbose, quiet, color, 
interactive, data_dir, cache_dir, config_file):
+def hatch(
+    ctx: click.Context,
+    env_name,
+    project,
+    verbose,
+    quiet,
+    color,
+    interactive,
+    data_dir,
+    cache_dir,
+    config_file,
+):
     """
     \b
      _   _       _       _
@@ -112,7 +123,6 @@
         interactive = False
 
     app = Application(ctx.exit, verbosity=verbose - quiet, enable_color=color, 
interactive=interactive)
-
     app.env_active = os.environ.get(AppEnvVars.ENV_ACTIVE)
     if (
         app.env_active
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/application.py 
new/hatch-hatch-v1.16.3/src/hatch/cli/application.py
--- old/hatch-hatch-v1.16.2/src/hatch/cli/application.py        2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/cli/application.py        2026-01-21 
02:32:44.000000000 +0100
@@ -47,8 +47,8 @@
     def get_environment(self, env_name: str | None = None) -> 
EnvironmentInterface:
         return self.project.get_environment(env_name)
 
-    def prepare_environment(self, environment: EnvironmentInterface):
-        self.project.prepare_environment(environment)
+    def prepare_environment(self, environment: EnvironmentInterface, *, 
keep_env: bool = False):
+        self.project.prepare_environment(environment, keep_env=keep_env)
 
     def run_shell_commands(self, context: ExecutionContext) -> None:
         with context.env.command_context():
@@ -91,6 +91,7 @@
         *,
         ignore_compat: bool = False,
         display_header: bool = False,
+        keep_env: bool = False,
     ) -> Generator[ExecutionContext, None, None]:
         if self.verbose or len(environments) > 1:
             display_header = True
@@ -117,7 +118,7 @@
                 context = ExecutionContext(environment)
                 yield context
 
-                self.prepare_environment(environment)
+                self.prepare_environment(environment, keep_env=keep_env)
                 self.execute_context(context)
 
         if incompatible:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/env/create.py 
new/hatch-hatch-v1.16.3/src/hatch/cli/env/create.py
--- old/hatch-hatch-v1.16.2/src/hatch/cli/env/create.py 2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/cli/env/create.py 2026-01-21 
02:32:44.000000000 +0100
@@ -1,9 +1,12 @@
 from __future__ import annotations
 
+import os
 from typing import TYPE_CHECKING
 
 import click
 
+from hatch.config.constants import AppEnvVars
+
 if TYPE_CHECKING:
     from hatch.cli.application import Application
 
@@ -35,7 +38,7 @@
 
             app.abort(f"Environment `{env}` is incompatible: {e}")
 
-        app.project.prepare_environment(environment)
+        app.project.prepare_environment(environment, 
keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))
 
     if incompatible:
         num_incompatible = len(incompatible)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/env/run.py 
new/hatch-hatch-v1.16.3/src/hatch/cli/env/run.py
--- old/hatch-hatch-v1.16.2/src/hatch/cli/env/run.py    2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/cli/env/run.py    2026-01-21 
02:32:44.000000000 +0100
@@ -1,9 +1,12 @@
 from __future__ import annotations
 
+import os
 from typing import TYPE_CHECKING
 
 import click
 
+from hatch.config.constants import AppEnvVars
+
 if TYPE_CHECKING:
     from hatch.cli.application import Application
 
@@ -135,6 +138,7 @@
         environments,
         ignore_compat=ignore_compat or matrix_selected,
         display_header=matrix_selected,
+        keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)),
     ):
         if context.env.name == "system":
             context.env.exists = lambda: True  # type: ignore[method-assign]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/cli/run/__init__.py 
new/hatch-hatch-v1.16.3/src/hatch/cli/run/__init__.py
--- old/hatch-hatch-v1.16.2/src/hatch/cli/run/__init__.py       2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/cli/run/__init__.py       2026-01-21 
02:32:44.000000000 +0100
@@ -90,21 +90,30 @@
             if "python" not in config and (requires_python := 
metadata.get("requires-python")) is not None:
                 import re
                 import sys
+                import sysconfig
 
                 from packaging.specifiers import SpecifierSet
 
                 from hatch.python.distributions import DISTRIBUTIONS
 
                 current_version = ".".join(map(str, sys.version_info[:2]))
+                if bool(sysconfig.get_config_var("Py_GIL_DISABLED")):
+                    current_version += "t"
+
+                # Strip "t" suffix for distribution lookup since DISTRIBUTIONS 
keys don't include it
+                current_version_base = current_version.rstrip("t")
                 distributions = [name for name in DISTRIBUTIONS if 
re.match(r"^\d+\.\d+$", name)]
-                distributions.sort(key=lambda name: name != current_version)
+                distributions.sort(key=lambda name: name != 
current_version_base)
 
                 python_constraint = SpecifierSet(requires_python)
                 for distribution in distributions:
                     # Try an artificially high patch version to account for
                     # common cases like `>=3.11.4` or `>=3.10,<3.11`
                     if python_constraint.contains(f"{distribution}.100"):
-                        config["python"] = distribution
+                        # Only set config["python"] if it doesn't match the 
current Python's base version
+                        # This allows free-threaded builds (e.g. 3.14t) to 
match their base version (3.14)
+                        if distribution != current_version_base:
+                            config["python"] = distribution
                         break
                 else:
                     app.abort(f"Unable to satisfy Python version constraint: 
{requires_python}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/config/constants.py 
new/hatch-hatch-v1.16.3/src/hatch/config/constants.py
--- old/hatch-hatch-v1.16.2/src/hatch/config/constants.py       2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/config/constants.py       2026-01-21 
02:32:44.000000000 +0100
@@ -9,6 +9,7 @@
     # https://no-color.org
     NO_COLOR = "NO_COLOR"
     FORCE_COLOR = "FORCE_COLOR"
+    KEEP_ENV = "HATCH_KEEP_ENV"
 
 
 class ConfigEnvVars:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/env/internal/test.py 
new/hatch-hatch-v1.16.3/src/hatch/env/internal/test.py
--- old/hatch-hatch-v1.16.2/src/hatch/env/internal/test.py      2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/env/internal/test.py      2026-01-21 
02:32:44.000000000 +0100
@@ -21,5 +21,5 @@
             "cov-combine": "coverage combine",
             "cov-report": "coverage report",
         },
-        "matrix": [{"python": ["3.14", "3.13", "3.12", "3.11", "3.10"]}],
+        "matrix": [{"python": ["3.14", "3.14t", "3.13", "3.12", "3.11", 
"3.10"]}],
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hatch-hatch-v1.16.2/src/hatch/env/plugin/interface.py 
new/hatch-hatch-v1.16.3/src/hatch/env/plugin/interface.py
--- old/hatch-hatch-v1.16.2/src/hatch/env/plugin/interface.py   2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/env/plugin/interface.py   2026-01-21 
02:32:44.000000000 +0100
@@ -369,6 +369,14 @@
             else:
                 all_dependencies_complex.append(Dependency(str(dep)))
 
+        if self.dependency_groups and not self.skip_install:
+            from hatch.utils.dep import get_complex_dependency_group
+
+            for dependency_group in self.dependency_groups:
+                all_dependencies_complex.extend(
+                    
get_complex_dependency_group(self.app.project.dependency_groups, 
dependency_group)
+                )
+
         if self.builder:
             from hatch.project.constants import BuildEnvVars
 
@@ -387,7 +395,7 @@
 
         # Ensure these are checked last to speed up initial environment 
creation since
         # they will already be installed along with the project
-        if self.dev_mode:
+        if self.dev_mode or self.features or self.dependency_groups:
             all_dependencies_complex.extend(self.project_dependencies_complex)
 
         return all_dependencies_complex
@@ -1235,9 +1243,9 @@
 
             # Now we have the necessary information to perform an optimized 
glob search for members
             members_found = False
-            for member_path in find_members(root, relative_path.split(os.sep)):
+            for member_path in find_members(shared_prefix, 
relative_path.split(os.sep)):
                 # Check if member should be excluded
-                relative_member_path = os.path.relpath(member_path, root)
+                relative_member_path = os.path.relpath(member_path, 
shared_prefix)
                 should_exclude = False
                 for exclude_pattern in exclude_patterns:
                     if fnmatch.fnmatch(relative_member_path, exclude_pattern) 
or fnmatch.fnmatch(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/env/virtual.py 
new/hatch-hatch-v1.16.3/src/hatch/env/virtual.py
--- old/hatch-hatch-v1.16.2/src/hatch/env/virtual.py    2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/env/virtual.py    2026-01-21 
02:32:44.000000000 +0100
@@ -2,6 +2,7 @@
 
 import os
 import sys
+import sysconfig
 from contextlib import contextmanager, nullcontext, suppress
 from functools import cached_property
 from os.path import isabs
@@ -15,6 +16,8 @@
 from hatch.utils.structures import EnvVars
 from hatch.venv.core import UVVirtualEnv, VirtualEnv
 
+FREETHREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
+
 if TYPE_CHECKING:
     from collections.abc import Callable, Iterable
 
@@ -118,7 +121,7 @@
             or is_default_environment(env_name, 
self.app.project.config.internal_envs[env_name])
         ):
             uv_env = self.app.project.get_environment(env_name)
-            self.app.project.prepare_environment(uv_env)
+            self.app.project.prepare_environment(uv_env, 
keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))
             with uv_env:
                 return self.platform.modules.shutil.which("uv")
 
@@ -204,11 +207,14 @@
 
             all_install_args = []
 
-            workspace_deps = [str(dep.path) for dep in 
self.local_dependencies_complex if dep.path]
+            workspace_deps = [dep for dep in self.local_dependencies_complex 
if dep.path]
             workspace_names = {dep.name.lower() for dep in 
self.local_dependencies_complex if dep.path}
 
-            for dep_path in workspace_deps:
-                all_install_args.extend(["--editable", dep_path])
+            for dep in workspace_deps:
+                if dep.editable:
+                    all_install_args.extend(["--editable", dep.path])
+                else:
+                    all_install_args.append(dep.path)
 
             standard_dependencies = []
 
@@ -279,7 +285,12 @@
 
     @cached_property
     def _preferred_python_version(self):
-        return f"{sys.version_info.major}.{sys.version_info.minor}"
+        version = f"{sys.version_info.major}.{sys.version_info.minor}"
+
+        if FREETHREADED_BUILD:
+            version += "t"
+
+        return version
 
     @cached_property
     def parent_python(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/project/core.py 
new/hatch-hatch-v1.16.3/src/hatch/project/core.py
--- old/hatch-hatch-v1.16.2/src/hatch/project/core.py   2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/project/core.py   2026-01-21 
02:32:44.000000000 +0100
@@ -180,43 +180,57 @@
             self.app,
         )
 
+    @staticmethod
+    @contextmanager
+    def managed_environment(
+        environment: EnvironmentInterface, *, keep_env: bool = False
+    ) -> Generator[EnvironmentInterface, None, None]:
+        """Context manager that removes environment on error unless keep_env 
is True."""
+        try:
+            yield environment
+        except Exception:
+            if not keep_env and environment.exists():
+                environment.remove()
+            raise
+
     # Ensure that this method is clearly written since it is
     # used for documenting the life cycle of environments.
-    def prepare_environment(self, environment: EnvironmentInterface):
+    def prepare_environment(self, environment: EnvironmentInterface, *, 
keep_env: bool):
         if not environment.exists():
-            self.env_metadata.reset(environment)
+            with self.managed_environment(environment, keep_env=keep_env):
+                self.env_metadata.reset(environment)
 
-            with environment.app_status_creation():
-                environment.create()
+                with environment.app_status_creation():
+                    environment.create()
 
-            if not environment.skip_install:
-                if environment.pre_install_commands:
-                    with environment.app_status_pre_installation():
-                        self.app.run_shell_commands(
-                            ExecutionContext(
-                                environment,
-                                
shell_commands=environment.pre_install_commands,
-                                source="pre-install",
-                                show_code_on_error=True,
+                if not environment.skip_install:
+                    if environment.pre_install_commands:
+                        with environment.app_status_pre_installation():
+                            self.app.run_shell_commands(
+                                ExecutionContext(
+                                    environment,
+                                    
shell_commands=environment.pre_install_commands,
+                                    source="pre-install",
+                                    show_code_on_error=True,
+                                )
                             )
-                        )
 
-                with environment.app_status_project_installation():
-                    if environment.dev_mode:
-                        environment.install_project_dev_mode()
-                    else:
-                        environment.install_project()
-
-                if environment.post_install_commands:
-                    with environment.app_status_post_installation():
-                        self.app.run_shell_commands(
-                            ExecutionContext(
-                                environment,
-                                
shell_commands=environment.post_install_commands,
-                                source="post-install",
-                                show_code_on_error=True,
+                    with environment.app_status_project_installation():
+                        if environment.dev_mode:
+                            environment.install_project_dev_mode()
+                        else:
+                            environment.install_project()
+
+                    if environment.post_install_commands:
+                        with environment.app_status_post_installation():
+                            self.app.run_shell_commands(
+                                ExecutionContext(
+                                    environment,
+                                    
shell_commands=environment.post_install_commands,
+                                    source="post-install",
+                                    show_code_on_error=True,
+                                )
                             )
-                        )
 
         with environment.app_status_dependency_state_check():
             new_dep_hash = environment.dependency_hash()
@@ -233,7 +247,7 @@
 
             self.env_metadata.update_dependency_hash(environment, new_dep_hash)
 
-    def prepare_build_environment(self, *, targets: list[str] | None = None) 
-> None:
+    def prepare_build_environment(self, *, targets: list[str] | None = None, 
keep_env: bool = False) -> None:
         from hatch.project.constants import BUILD_BACKEND, BuildEnvVars
         from hatch.utils.structures import EnvVars
 
@@ -249,7 +263,7 @@
                 except Exception as e:  # noqa: BLE001
                     self.app.abort(f"Environment `{self.build_env.name}` is 
incompatible: {e}")
 
-            self.prepare_environment(self.build_env)
+            self.prepare_environment(self.build_env, keep_env=keep_env)
 
             additional_dependencies: list[str] = []
             with self.app.status("Inspecting build dependencies"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/src/hatch/publish/auth.py 
new/hatch-hatch-v1.16.3/src/hatch/publish/auth.py
--- old/hatch-hatch-v1.16.2/src/hatch/publish/auth.py   2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/src/hatch/publish/auth.py   2026-01-21 
02:32:44.000000000 +0100
@@ -46,7 +46,8 @@
 
         import keyring
 
-        password = keyring.get_password(self._repo, self.username)
+        keyring_service = self._repo_config["url"]
+        password = keyring.get_password(keyring_service, self.username)
         if password is not None:
             return password
 
@@ -111,4 +112,5 @@
         if self.__password_was_read:
             import keyring
 
-            keyring.set_password(self._repo, self.__username, self.__password)
+            keyring_service = self._repo_config["url"]
+            keyring.set_password(keyring_service, self.__username, 
self.__password)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/cli/env/test_create.py 
new/hatch-hatch-v1.16.3/tests/cli/env/test_create.py
--- old/hatch-hatch-v1.16.2/tests/cli/env/test_create.py        2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/tests/cli/env/test_create.py        2026-01-21 
02:32:44.000000000 +0100
@@ -1076,7 +1076,9 @@
     data_path.mkdir()
 
     project = Project(project_path)
-    helpers.update_project_environment(project, "default", {"dev-mode": False, 
**project.config.envs["default"]})
+    helpers.update_project_environment(
+        project, "default", {"dev-mode": False, "extra-dependencies": 
["binary"], **project.config.envs["default"]}
+    )
     helpers.update_project_environment(project, "test", {})
 
     with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
@@ -1088,6 +1090,7 @@
         Creating environment: test
         Installing project
         Checking dependencies
+        Syncing dependencies
         """
     )
 
@@ -1116,8 +1119,8 @@
         )
         requirements = extract_installed_requirements(output.splitlines())
 
-        assert len(requirements) == 1
-        assert requirements[0].lower() == f"my-app @ 
{project_path.as_uri().lower()}"
+        assert len(requirements) == 2
+        assert f"my-app @ {project_path.as_uri().lower()}" in [req.lower() for 
req in requirements]
 
 
 @pytest.mark.requires_internet
@@ -2032,3 +2035,81 @@
         assert requirements[1].lower() == f"-e 
{project_path.as_uri().lower()}/baz"
         assert requirements[2].lower() == f"-e 
{project_path.as_uri().lower()}/foo"
         assert requirements[3].lower() == f"-e {project_path.as_uri().lower()}"
+
+
[email protected]_internet
+def test_workspace_members_always_editable_with_dev_mode_false(
+    hatch, helpers, temp_dir, platform, uv_on_path, 
extract_installed_requirements
+):
+    """Verify workspace members are always installed as editable even when 
dev-mode=false."""
+    project_name = "My.App"
+
+    with temp_dir.as_cwd():
+        result = hatch("new", project_name)
+        assert result.exit_code == 0, result.output
+
+    project_path = temp_dir / "my-app"
+    data_path = temp_dir / "data"
+    data_path.mkdir()
+
+    # Create workspace members
+    members = ["member-a", "member-b"]
+    for member in members:
+        with project_path.as_cwd():
+            result = hatch("new", member)
+            assert result.exit_code == 0, result.output
+
+    project = Project(project_path)
+    helpers.update_project_environment(
+        project,
+        "default",
+        {
+            "dev-mode": False,
+            "workspace": {"members": members},
+            **project.config.envs["default"],
+        },
+    )
+
+    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
+        result = hatch("env", "create", "default")
+
+    assert result.exit_code == 0, result.output
+
+    env_data_path = data_path / "env" / "virtual"
+    project_data_path = env_data_path / project_path.name
+    storage_dirs = list(project_data_path.iterdir())
+    storage_path = storage_dirs[0]
+    env_dirs = list(storage_path.iterdir())
+    env_path = env_dirs[0]
+
+    with UVVirtualEnv(env_path, platform):
+        output = platform.run_command([uv_on_path, "pip", "freeze"], 
check=True, capture_output=True).stdout.decode(
+            "utf-8"
+        )
+        requirements = extract_installed_requirements(output.splitlines())
+
+        # Find project and member requirements - be more precise
+        my_app_reqs = [
+            r
+            for r in requirements
+            if (r.lower().startswith("-e file:") and 
r.lower().endswith("/my-app"))
+            or (r.lower().startswith("my-app @") and "/member-" not in 
r.lower())
+        ]
+        member_a_reqs = [r for r in requirements if "member-a" in r.lower() 
and "/member-a" in r.lower()]
+        member_b_reqs = [r for r in requirements if "member-b" in r.lower() 
and "/member-b" in r.lower()]
+
+        assert len(my_app_reqs) == 1, f"Expected 1 my-app requirement, got 
{len(my_app_reqs)}: {my_app_reqs}"
+        assert len(member_a_reqs) == 1, f"Expected 1 member-a requirement, got 
{len(member_a_reqs)}: {member_a_reqs}"
+        assert len(member_b_reqs) == 1, f"Expected 1 member-b requirement, got 
{len(member_b_reqs)}: {member_b_reqs}"
+
+        my_app_req = my_app_reqs[0]
+        member_a_req = member_a_reqs[0]
+        member_b_req = member_b_reqs[0]
+
+        # Project should NOT be editable (dev-mode=false)
+        assert not my_app_req.lower().startswith("-e"), f"Project should not 
be editable: {my_app_req}"
+        assert my_app_req.lower().startswith("my-app @"), f"Project should be 
non-editable install: {my_app_req}"
+
+        # Workspace members MUST be editable (always)
+        assert member_a_req.lower().startswith("-e"), f"Member A should be 
editable: {member_a_req}"
+        assert member_b_req.lower().startswith("-e"), f"Member B should be 
editable: {member_b_req}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/cli/env/test_show.py 
new/hatch-hatch-v1.16.3/tests/cli/env/test_show.py
--- old/hatch-hatch-v1.16.2/tests/cli/env/test_show.py  2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/tests/cli/env/test_show.py  2026-01-21 
02:32:44.000000000 +0100
@@ -72,6 +72,7 @@
         "hatch-build",
         "hatch-static-analysis",
         "hatch-test.py3.14",
+        "hatch-test.py3.14t",
         "hatch-test.py3.13",
         "hatch-test.py3.12",
         "hatch-test.py3.11",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hatch-hatch-v1.16.2/tests/cli/run/test_run.py 
new/hatch-hatch-v1.16.3/tests/cli/run/test_run.py
--- old/hatch-hatch-v1.16.2/tests/cli/run/test_run.py   2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/tests/cli/run/test_run.py   2026-01-21 
02:32:44.000000000 +0100
@@ -1,5 +1,6 @@
 import os
 import sys
+import sysconfig
 
 import pytest
 
@@ -11,6 +12,8 @@
 from hatch.utils.structures import EnvVars
 from hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE
 
+FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
+
 
 @pytest.fixture(scope="module")
 def available_python_version():
@@ -1214,6 +1217,9 @@
     data_path.mkdir()
 
     known_version = "".join(map(str, sys.version_info[:2]))
+    if FREE_THREADED_BUILD:
+        known_version += "t"
+
     project = Project(project_path)
     helpers.update_project_environment(project, "default", {"skip-install": 
True, **project.config.envs["default"]})
     helpers.update_project_environment(project, "test", {"matrix": [{"python": 
[known_version, "9000"]}]})
@@ -1222,15 +1228,18 @@
         result = hatch(
             "run", "test:python", "-c", "import os,sys;open('test.txt', 
'a').write(sys.executable+os.linesep[-1])"
         )
-
     padding = "─"
-    if len(known_version) < 3:
-        padding += "─"
+    if FREE_THREADED_BUILD:
+        pre_padding = ""
+    else:
+        pre_padding = "─"
+        if len(known_version) < 3:
+            padding += "─"
 
     assert result.exit_code == 0, result.output
     assert result.output == helpers.dedent(
         f"""
-        ────────────────────────────────── test.py{known_version} 
─────────────────────────────────{padding}
+        ─────────────────────────────────{pre_padding} test.py{known_version} 
─────────────────────────────────{padding}
         Creating environment: test.py{known_version}
         Checking dependencies
 
@@ -2575,6 +2584,9 @@
         # Use the current minor version so that the current Python
         # will be used and distributions don't have to be downloaded
         major, minor = sys.version_info[:2]
+        python_version = f"{major}.{minor}"
+        if FREE_THREADED_BUILD:
+            python_version += "t"
 
         script.write_text(
             helpers.dedent(
@@ -2583,7 +2595,7 @@
                 # requires-python = ">9000"
                 #
                 # [tool.hatch]
-                # python = "{major}.{minor}"
+                # python = "{python_version}"
                 # ///
                 import pathlib
                 import sys
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hatch-hatch-v1.16.2/tests/env/plugin/test_interface.py 
new/hatch-hatch-v1.16.3/tests/env/plugin/test_interface.py
--- old/hatch-hatch-v1.16.2/tests/env/plugin/test_interface.py  2025-12-06 
19:55:09.000000000 +0100
+++ new/hatch-hatch-v1.16.3/tests/env/plugin/test_interface.py  2026-01-21 
02:32:44.000000000 +0100
@@ -1541,6 +1541,309 @@
         # Should have my-app[test] which will cause pip to install pytest
         assert any("pytest" in dep.lower() for dep in all_deps_str)
 
+    def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, 
platform, temp_application):
+        """Verify dev-mode=true creates editable local dependency."""
+        # Create a pyproject.toml file so skip_install defaults to False
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [tool.hatch.envs.default]
+    dev-mode = true
+    """)
+
+        project = Project(temp_dir)
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+
+        assert len(local_deps) == 1
+        assert local_deps[0].editable is True
+
+    def test_dev_mode_false_returns_non_editable(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify dev-mode=false creates non-editable local dependency."""
+        # Create a pyproject.toml file so skip_install defaults to False
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [tool.hatch.envs.default]
+    dev-mode = false
+    """)
+
+        project = Project(temp_dir)
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+
+        assert len(local_deps) == 1
+        assert local_deps[0].editable is False
+
+    def test_skip_install_returns_empty(self, temp_dir, isolated_data_dir, 
platform, temp_application):
+        """Verify skip-install=true returns empty local dependencies."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [tool.hatch.envs.default]
+    skip-install = true
+    """)
+
+        project = Project(temp_dir)
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+
+        assert len(local_deps) == 0
+
+    def test_workspace_members_always_editable(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify workspace members are always editable regardless of 
dev-mode."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [tool.hatch.envs.default]
+    dev-mode = false
+    """)
+
+        project = Project(temp_dir)
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+
+        # Project itself should respect dev-mode=false
+        assert len(local_deps) == 1
+        assert local_deps[0].editable is False
+
+    def test_dependency_group_resolution(self, temp_dir, isolated_data_dir, 
platform, temp_application):
+        """Test dependency group resolution."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [dependency-groups]
+    test = ["pytest"]
+
+    [tool.hatch.envs.default]
+    dependency-groups = ["test"]
+    """)
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        deps = environment.project_dependencies_complex
+        assert any("pytest" in str(d) for d in deps)
+
+    def test_dependency_group_resolution_builder_false_dev_mode_false(
+        self, temp_dir, isolated_data_dir, platform, temp_application
+    ):
+        """Test dependency group resolution in non-builder non-dev-mode 
environments."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [dependency-groups]
+    test = ["pytest"]
+
+    [tool.hatch.envs.default]
+    builder = false
+    dev-mode = false
+    dependency-groups = ["test"]
+    """)
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        assert any("pytest" in str(d) for d in 
environment.project_dependencies_complex)
+        assert any("pytest" in str(d) for d in 
environment.dependencies_complex)
+
+    def test_dependency_group_resolution_builder_true_dev_mode_false(
+        self, temp_dir, isolated_data_dir, platform, temp_application
+    ):
+        """Test dependency group resolution in builder non-dev-mode 
environments."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [dependency-groups]
+    test = ["pytest"]
+
+    [tool.hatch.envs.default]
+    builder = true
+    dev-mode = false
+    dependency-groups = ["test"]
+    """)
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        assert any("pytest" in str(d) for d in 
environment.project_dependencies_complex)
+        assert any("pytest" in str(d) for d in 
environment.dependencies_complex)
+
+    def test_dependency_group_resolution_builder_true_dev_mode_true(
+        self, temp_dir, isolated_data_dir, platform, temp_application
+    ):
+        """Test dependency group resolution in builder dev-mode 
environments."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [dependency-groups]
+    test = ["pytest"]
+
+    [tool.hatch.envs.default]
+    builder = true
+    dev-mode = true
+    dependency-groups = ["test"]
+    """)
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        assert any("pytest" in str(d) for d in 
environment.project_dependencies_complex)
+        assert any("pytest" in str(d) for d in 
environment.dependencies_complex)
+
+    def test_additional_dependencies_as_strings(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Test additional_dependencies with string values."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+    """)
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        environment.additional_dependencies = ["extra-dep"]
+        deps = environment.dependencies_complex
+        assert any("extra-dep" in str(d) for d in deps)
+
 
 class TestScripts:
     @pytest.mark.parametrize("field", ["scripts", "extra-scripts"])
@@ -2275,6 +2578,37 @@
 
         assert list(environment.expand_command("foo")) == [command]
 
+    def test_verbosity_flag_adjustment_invalid(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Test verbosity flag with invalid adjustment."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+    [project]
+    name = "my-app"
+    version = "0.0.1"
+
+    [tool.hatch.envs.default]
+    scripts.test = "pytest {verbosity:flag:invalid}"
+    """)
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        with pytest.raises(TypeError, match="Verbosity flag adjustment must be 
an integer"):
+            list(environment.expand_command("test"))
+
     def test_args_undefined(self, isolation, isolated_data_dir, platform, 
global_application):
         config = {
             "project": {"name": "my_app", "version": "0.0.1"},
@@ -2991,3 +3325,369 @@
             "pkg-feature-22",
             "pkg-feature-32",
         ]
+
+
+class TestDependencyHash:
+    def test_hash_includes_local_dependencies(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify dependency hash includes local dependencies."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+""")
+
+        config = {"project": {"name": "my_app", "version": "0.0.1"}}
+        project = Project(temp_dir, config=config)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        hash_value = environment.dependency_hash()
+        assert hash_value
+        assert len(hash_value) > 0
+
+    def test_hash_stable_when_dependencies_unchanged(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify dependency hash is stable when dependencies don't change."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+""")
+
+        config = {"project": {"name": "my_app", "version": "0.0.1"}}
+        project = Project(temp_dir, config=config)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        hash1 = environment.dependency_hash()
+        hash2 = environment.dependency_hash()
+
+        assert hash1 == hash2
+
+    def test_hash_changes_with_extra_dependencies(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify dependency hash changes when extra-dependencies are added."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+""")
+
+        config_no_deps = {"project": {"name": "my_app", "version": "0.0.1"}}
+        project_no_deps = Project(temp_dir, config=config_no_deps)
+        project_no_deps.set_app(temp_application)
+        temp_application.project = project_no_deps
+        env_no_deps = MockEnvironment(
+            temp_dir,
+            project_no_deps.metadata,
+            "default",
+            project_no_deps.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+        hash_no_deps = env_no_deps.dependency_hash()
+
+        config_with_deps = {
+            "project": {"name": "my_app", "version": "0.0.1"},
+            "tool": {"hatch": {"envs": {"default": {"extra-dependencies": 
["pytest"]}}}},
+        }
+        project_with_deps = Project(temp_dir, config=config_with_deps)
+        project_with_deps.set_app(temp_application)
+        temp_application.project = project_with_deps
+        env_with_deps = MockEnvironment(
+            temp_dir,
+            project_with_deps.metadata,
+            "default",
+            project_with_deps.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+        hash_with_deps = env_with_deps.dependency_hash()
+
+        assert hash_no_deps != hash_with_deps
+
+
+class TestLocalDependenciesComplex:
+    def test_dev_mode_true_returns_editable(self, temp_dir, isolated_data_dir, 
platform, temp_application):
+        """Verify dev-mode=true creates editable local dependency."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+        assert len(local_deps) == 1
+        assert local_deps[0].editable is True
+
+    def test_dev_mode_false_returns_non_editable(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify dev-mode=false creates non-editable local dependency."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+
+[tool.hatch.envs.default]
+dev-mode = false
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+        assert len(local_deps) == 1
+        assert local_deps[0].editable is False
+
+    def test_workspace_members_always_editable(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify workspace members are always editable regardless of 
dev-mode."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+
+[tool.hatch.envs.default]
+dev-mode = false
+workspace.members = ["member"]
+""")
+
+        member_dir = temp_dir / "member"
+        member_dir.mkdir()
+        (member_dir / "pyproject.toml").write_text("""
+[project]
+name = "member"
+version = "0.0.1"
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        local_deps = environment.local_dependencies_complex
+        assert len(local_deps) == 2
+        project_dep = next(d for d in local_deps if d.name == "my-app")
+        member_dep = next(d for d in local_deps if d.name == "member")
+        assert project_dep.editable is False
+        assert member_dep.editable is True
+
+
+class TestDynamicDependencies:
+    def test_dynamic_dependencies_resolved(self, temp_dir, isolated_data_dir, 
platform, temp_application):
+        """Verify dynamic dependencies are resolved correctly."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+dynamic = ["dependencies"]
+
+[tool.hatch.metadata]
+allow-direct-references = true
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        assert "dependencies" in environment.metadata.dynamic
+
+
+class TestBuildSystemIntegration:
+    def test_builder_includes_build_requirements(self, temp_dir, 
isolated_data_dir, platform, temp_application):
+        """Verify builder environment includes build system requirements."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[build-system]
+requires = ["hatchling", "build-dep"]
+
+[project]
+name = "my-app"
+version = "0.0.1"
+
+[tool.hatch.envs.build]
+builder = true
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "build",
+            project.config.envs["build"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        deps = environment.dependencies
+        assert any("hatchling" in d for d in deps)
+        assert any("build-dep" in d for d in deps)
+
+
+class TestEnvironmentLifecycle:
+    def test_app_status_contexts(self, temp_dir, isolated_data_dir, platform, 
temp_application):
+        """Verify environment lifecycle status contexts work correctly."""
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        with environment.app_status_creation():
+            pass
+        with environment.app_status_pre_installation():
+            pass
+        with environment.app_status_post_installation():
+            pass
+        with environment.app_status_project_installation():
+            pass
+        with environment.app_status_dependency_state_check():
+            pass
+        with environment.app_status_dependency_installation_check():
+            pass
+        with environment.app_status_dependency_synchronization():
+            pass
+
+
+class TestFileSystemContext:
+    def test_join_creates_new_context(self, temp_dir, isolated_data_dir, 
platform, temp_application):
+        """Test FileSystemContext.join creates proper paths."""
+        from hatch.env.plugin.interface import FileSystemContext
+
+        pyproject = temp_dir / "pyproject.toml"
+        pyproject.write_text("""
+[project]
+name = "my-app"
+version = "0.0.1"
+""")
+
+        project = Project(temp_dir)
+        project.set_app(temp_application)
+        temp_application.project = project
+        environment = MockEnvironment(
+            temp_dir,
+            project.metadata,
+            "default",
+            project.config.envs["default"],
+            {},
+            isolated_data_dir,
+            isolated_data_dir,
+            platform,
+            0,
+            temp_application,
+        )
+
+        ctx = FileSystemContext(environment, local_path=temp_dir, 
env_path="/env")
+        new_ctx = ctx.join("subdir")
+        assert "subdir" in str(new_ctx.local_path)
+        assert "subdir" in new_ctx.env_path








++++++ shell.patch ++++++
>From 9068758886e57751ff71a59520dcf576c7deea0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=CF=87risto=CF=86e=20=D0=94=D0=B5=D0=BC=D0=BA=D0=BE?=
 <[email protected]>
Date: Fri, 23 Jan 2026 17:44:54 +0100
Subject: [PATCH] Fix #2164 keep_env type error for hatch shell (#2165)

* Fix #2164

* Fix ruff

---------

Co-authored-by: Cary Hawkins <[email protected]>
---
 src/hatch/cli/shell/__init__.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/hatch/cli/shell/__init__.py b/src/hatch/cli/shell/__init__.py
index 815e71d98..5edc1f1ca 100644
--- a/src/hatch/cli/shell/__init__.py
+++ b/src/hatch/cli/shell/__init__.py
@@ -1,9 +1,12 @@
 from __future__ import annotations
 
+import os
 from typing import TYPE_CHECKING
 
 import click
 
+from hatch.config.constants import AppEnvVars
+
 if TYPE_CHECKING:
     from hatch.cli.application import Application
 
@@ -42,7 +45,7 @@ def shell(app: Application, env_name: str | None, name: str, 
path: str):  # no c
 
     with app.project.ensure_cwd():
         environment = app.project.get_environment(chosen_env)
-        app.project.prepare_environment(environment)
+        app.project.prepare_environment(environment, 
keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))
 
         first_run_indicator = app.cache_dir / "shell" / "first_run"
         if not first_run_indicator.is_file():

Reply via email to