Craft Parts does not support Python 3.14
Metadata
Current evaluation
Python 3.14 chroot failures were fixed by explicitly using the multiprocessing fork context in #1672 (commit 12da4851).
Resolution: closed resolved
Reason: The resolving change is commit 12da4851, merged via PR #1672, titled “fix: use fork context for chroot in python 3.14.” It adds multiprocessing.get_context("fork") for chroot execution, matching the diagnosis that Python 3.14 changed the default to forkserver.
Evaluated by: mr-cal-bot
No scores available.
Issue body
### Bug Description
Craft Parts errors out on Python 3.14 due to the change in the way it handles threads. It is unclear whether this is just to do with testing or if it's actually a failure.
### To Reproduce
Run the chroot unit tests (tests/unit/overlays/test_chroot.py) without root in Python 3.12 (succeeds) and Python 3.14 (fails)
### part yaml
```shell
```
### Relevant log output
```shell
$ .venv/bin/python -m pytest tests/unit/overlays/test_chroot.py
================================================================================================================== test session starts ==================================================================================================================
platform linux -- Python 3.14.6, pytest-9.0.3, pluggy-1.6.0
rootdir: /home/lengau/Work/Code/craft-parts
configfile: pyproject.toml
plugins: mock-3.15.1, check-2.7.6, cov-7.0.0, rerunfailures-16.1, subprocess-1.5.3, requests-mock-1.12.1, hypothesis-6.151.9, pyfakefs-6.1.6, anyio-4.12.1
collected 7 items
tests/unit/overlays/test_chroot.py FFFF..F [100%]
======================================================================================================================= FAILURES ========================================================================================================================
________________________________________________________________________________________________________________ TestChroot.test_chroot _________________________________________________________________________________________________________________
self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c49967390>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c498856a0>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot0')
mock_chroot = <Mock spec='builtin_function_or_method' id='135567581408832'>
def test_chroot(self, mocker, new_dir, mock_chroot):
mock_mount = mocker.patch("craft_parts.utils.os_utils.mount")
mock_umount = mocker.patch("craft_parts.utils.os_utils.umount")
spy_process = mocker.spy(multiprocessing, "Process")
new_root = Path(new_dir, "dir1")
# this runs in the child process
Path("dir1").mkdir()
for subdir in ["etc", "proc", "sys", "dev", "dev/shm"]:
Path(new_root, subdir).mkdir()
> chroot.chroot(new_root, target_func, "content")
/home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:65:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {}
parent_conn = <multiprocessing.connection.Connection object at 0x7b4c49718440>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c497687d0>, child = <Process name='Process-1' pid=3401899 parent=3401891 stopped exitcode=0>
res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1'"
def chroot(
path: Path,
target: Callable[..., _T],
*args: Any,
use_host_sources: bool = False,
**kwargs: Any,
) -> _T:
"""Execute a callable in a chroot environment.
:param path: The new filesystem root.
:param target: The callable to run in the chroot environment.
:param args: Arguments for target.
:param kwargs: Keyword arguments for target.
:param use_host_sources: Whether overlay steps should also include the repository
sources defined on the host.
:returns: The target function return value.
"""
logger.debug("[pid=%d] parent process", os.getpid())
# This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]"
# However, types surrounding multiprocessing are finnicky at best and the way we handle the
# result here makes the typehint effectively true, since we don't attempt to access the first
# field of the tuple unless the second field is None.
parent_conn: Connection[Any, tuple[_T, str | None]]
parent_conn, child_conn = multiprocessing.Pipe()
child = multiprocessing.Process(
target=_runner, args=(Path(path), child_conn, target, args, kwargs)
)
logger.debug("[pid=%d] set up chroot", os.getpid())
_setup_chroot(path, use_host_sources=use_host_sources)
try:
child.start()
res, err = parent_conn.recv()
child.join()
finally:
logger.debug("[pid=%d] clean up chroot", os.getpid())
_cleanup_chroot(path, use_host_sources=use_host_sources)
if isinstance(err, str):
> raise errors.OverlayChrootExecutionError(err)
E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1'
/home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError
------------------------------------------------------------------------------------------------------------------ Captured log setup -------------------------------------------------------------------------------------------------------------------
WARNING root:features.py:42 deleting current features configuration
_________________________________________________________________________________________________________ TestChroot.test_chroot_no_mountpoints _________________________________________________________________________________________________________
self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c49967d90>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c497682d0>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0')
def test_chroot_no_mountpoints(self, mocker, new_dir):
mock_mount = mocker.patch("craft_parts.utils.os_utils.mount")
mock_umount = mocker.patch("craft_parts.utils.os_utils.umount")
spy_process = mocker.spy(multiprocessing, "Process")
new_root = Path(new_dir, "dir1")
# this runs in the child process
mocker.patch("os.chroot")
Path("dir1").mkdir()
> chroot.chroot(new_root, target_func, "content")
/home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:102:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {}
parent_conn = <multiprocessing.connection.Connection object at 0x7b4c498afce0>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c497a4640>, child = <Process name='Process-2' pid=3401900 parent=3401891 stopped exitcode=0>
res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1'"
def chroot(
path: Path,
target: Callable[..., _T],
*args: Any,
use_host_sources: bool = False,
**kwargs: Any,
) -> _T:
"""Execute a callable in a chroot environment.
:param path: The new filesystem root.
:param target: The callable to run in the chroot environment.
:param args: Arguments for target.
:param kwargs: Keyword arguments for target.
:param use_host_sources: Whether overlay steps should also include the repository
sources defined on the host.
:returns: The target function return value.
"""
logger.debug("[pid=%d] parent process", os.getpid())
# This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]"
# However, types surrounding multiprocessing are finnicky at best and the way we handle the
# result here makes the typehint effectively true, since we don't attempt to access the first
# field of the tuple unless the second field is None.
parent_conn: Connection[Any, tuple[_T, str | None]]
parent_conn, child_conn = multiprocessing.Pipe()
child = multiprocessing.Process(
target=_runner, args=(Path(path), child_conn, target, args, kwargs)
)
logger.debug("[pid=%d] set up chroot", os.getpid())
_setup_chroot(path, use_host_sources=use_host_sources)
try:
child.start()
res, err = parent_conn.recv()
child.join()
finally:
logger.debug("[pid=%d] clean up chroot", os.getpid())
_cleanup_chroot(path, use_host_sources=use_host_sources)
if isinstance(err, str):
> raise errors.OverlayChrootExecutionError(err)
E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1'
/home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError
------------------------------------------------------------------------------------------------------------------- Captured log call -------------------------------------------------------------------------------------------------------------------
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/etc/resolv.conf') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/proc') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/sys') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/dev') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/dev') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/sys') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/proc') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/etc/resolv.conf') not found!
_____________________________________________________________________________________________________ TestChroot.test_chroot_symlinked_resolv_conf ______________________________________________________________________________________________________
self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c498ac8a0>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c4976b110>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0')
def test_chroot_symlinked_resolv_conf(self, mocker, new_dir):
mock_mount = mocker.patch("craft_parts.utils.os_utils.mount")
mock_umount = mocker.patch("craft_parts.utils.os_utils.umount")
spy_process = mocker.spy(multiprocessing, "Process")
new_root = Path(new_dir, "dir1")
# this runs in the child process
mocker.patch("os.chroot")
Path("dir1").mkdir()
Path("dir1/etc").mkdir()
Path("dir1/etc/resolv.conf").symlink_to("whatever")
> chroot.chroot(new_root, target_func, "content")
/home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:127:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {}
parent_conn = <multiprocessing.connection.Connection object at 0x7b4c4974e7a0>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c4974e140>, child = <Process name='Process-3' pid=3401901 parent=3401891 stopped exitcode=0>
res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1'"
def chroot(
path: Path,
target: Callable[..., _T],
*args: Any,
use_host_sources: bool = False,
**kwargs: Any,
) -> _T:
"""Execute a callable in a chroot environment.
:param path: The new filesystem root.
:param target: The callable to run in the chroot environment.
:param args: Arguments for target.
:param kwargs: Keyword arguments for target.
:param use_host_sources: Whether overlay steps should also include the repository
sources defined on the host.
:returns: The target function return value.
"""
logger.debug("[pid=%d] parent process", os.getpid())
# This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]"
# However, types surrounding multiprocessing are finnicky at best and the way we handle the
# result here makes the typehint effectively true, since we don't attempt to access the first
# field of the tuple unless the second field is None.
parent_conn: Connection[Any, tuple[_T, str | None]]
parent_conn, child_conn = multiprocessing.Pipe()
child = multiprocessing.Process(
target=_runner, args=(Path(path), child_conn, target, args, kwargs)
)
logger.debug("[pid=%d] set up chroot", os.getpid())
_setup_chroot(path, use_host_sources=use_host_sources)
try:
child.start()
res, err = parent_conn.recv()
child.join()
finally:
logger.debug("[pid=%d] clean up chroot", os.getpid())
_cleanup_chroot(path, use_host_sources=use_host_sources)
if isinstance(err, str):
> raise errors.OverlayChrootExecutionError(err)
E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1'
/home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError
------------------------------------------------------------------------------------------------------------------- Captured log call -------------------------------------------------------------------------------------------------------------------
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/proc') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/sys') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/dev') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/dev') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/sys') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/proc') not found!
_________________________________________________________________________________________________________ TestChroot.test_chroot_no_resolv_conf _________________________________________________________________________________________________________
self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c498acc30>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c497a4770>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0')
def test_chroot_no_resolv_conf(self, mocker, new_dir):
mock_mount = mocker.patch("craft_parts.utils.os_utils.mount")
mock_umount = mocker.patch("craft_parts.utils.os_utils.umount")
spy_process = mocker.spy(multiprocessing, "Process")
new_root = Path(new_dir, "dir1")
# this runs in the child process
mocker.patch("os.chroot")
Path("dir1").mkdir()
Path("dir1/etc").mkdir()
> chroot.chroot(new_root, target_func, "content")
/home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:156:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {}
parent_conn = <multiprocessing.connection.Connection object at 0x7b4c497d4450>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c497cb5c0>, child = <Process name='Process-4' pid=3401902 parent=3401891 stopped exitcode=0>
res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1'"
def chroot(
path: Path,
target: Callable[..., _T],
*args: Any,
use_host_sources: bool = False,
**kwargs: Any,
) -> _T:
"""Execute a callable in a chroot environment.
:param path: The new filesystem root.
:param target: The callable to run in the chroot environment.
:param args: Arguments for target.
:param kwargs: Keyword arguments for target.
:param use_host_sources: Whether overlay steps should also include the repository
sources defined on the host.
:returns: The target function return value.
"""
logger.debug("[pid=%d] parent process", os.getpid())
# This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]"
# However, types surrounding multiprocessing are finnicky at best and the way we handle the
# result here makes the typehint effectively true, since we don't attempt to access the first
# field of the tuple unless the second field is None.
parent_conn: Connection[Any, tuple[_T, str | None]]
parent_conn, child_conn = multiprocessing.Pipe()
child = multiprocessing.Process(
target=_runner, args=(Path(path), child_conn, target, args, kwargs)
)
logger.debug("[pid=%d] set up chroot", os.getpid())
_setup_chroot(path, use_host_sources=use_host_sources)
try:
child.start()
res, err = parent_conn.recv()
child.join()
finally:
logger.debug("[pid=%d] clean up chroot", os.getpid())
_cleanup_chroot(path, use_host_sources=use_host_sources)
if isinstance(err, str):
> raise errors.OverlayChrootExecutionError(err)
E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1'
/home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError
------------------------------------------------------------------------------------------------------------------- Captured log call -------------------------------------------------------------------------------------------------------------------
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/proc') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/sys') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/dev') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/dev') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/sys') not found!
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/proc') not found!
________________________________________________________________________________________________________ TestChroot.test_chroot_use_host_sources ________________________________________________________________________________________________________
self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c498717b0>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c4974e030>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0')
mock_chroot = <Mock spec='builtin_function_or_method' id='135567579906800'>
def test_chroot_use_host_sources(self, mocker, new_dir, mock_chroot):
mock_mount = mocker.patch("craft_parts.utils.os_utils.mount")
mock_umount = mocker.patch("craft_parts.utils.os_utils.umount")
mock_copytree = mocker.patch("shutil.copytree")
spy_process = mocker.spy(multiprocessing, "Process")
new_root = Path(new_dir, "dir1")
# this runs in the child process
Path("dir1").mkdir()
for subdir in ["etc", "proc", "sys", "dev", "dev/shm"]:
Path(new_root, subdir).mkdir()
> chroot.chroot(new_root, target_func, "content", use_host_sources=True)
/home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:205:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = True, args = ('content',), kwargs = {}
parent_conn = <multiprocessing.connection.Connection object at 0x7b4c498d98d0>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c498d99b0>, child = <Process name='Process-5' pid=3401903 parent=3401891 stopped exitcode=0>
res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1'"
def chroot(
path: Path,
target: Callable[..., _T],
*args: Any,
use_host_sources: bool = False,
**kwargs: Any,
) -> _T:
"""Execute a callable in a chroot environment.
:param path: The new filesystem root.
:param target: The callable to run in the chroot environment.
:param args: Arguments for target.
:param kwargs: Keyword arguments for target.
:param use_host_sources: Whether overlay steps should also include the repository
sources defined on the host.
:returns: The target function return value.
"""
logger.debug("[pid=%d] parent process", os.getpid())
# This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]"
# However, types surrounding multiprocessing are finnicky at best and the way we handle the
# result here makes the typehint effectively true, since we don't attempt to access the first
# field of the tuple unless the second field is None.
parent_conn: Connection[Any, tuple[_T, str | None]]
parent_conn, child_conn = multiprocessing.Pipe()
child = multiprocessing.Process(
target=_runner, args=(Path(path), child_conn, target, args, kwargs)
)
logger.debug("[pid=%d] set up chroot", os.getpid())
_setup_chroot(path, use_host_sources=use_host_sources)
try:
child.start()
res, err = parent_conn.recv()
child.join()
finally:
logger.debug("[pid=%d] clean up chroot", os.getpid())
_cleanup_chroot(path, use_host_sources=use_host_sources)
if isinstance(err, str):
> raise errors.OverlayChrootExecutionError(err)
E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1'
/home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError
------------------------------------------------------------------------------------------------------------------- Captured log call -------------------------------------------------------------------------------------------------------------------
WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1/usr/share/keyrings') not found. Skipping
WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1/usr/share/keyrings') not found!
----------------------------------------------------------------------------------------------------------------- Captured log teardown -----------------------------------------------------------------------------------------------------------------
WARNING root:features.py:42 deleting current features configuration
================================================================================================================ short test summary info ================================================================================================================
FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1'
FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_no_mountpoints - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1'
FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_symlinked_resolv_conf - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1'
FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_no_resolv_conf - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1'
FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_use_host_sources - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1'
============================================================================================================== 5 failed, 2 passed in 2.64s ==============================================================================================================
```
Evaluation history
| Date | Model | Scores | Action | Summary |
|---|---|---|---|---|
| stealth/space-bunny-alpha | — | closed resolved | Python 3.14 chroot failures were fixed by explicitly using the multiprocessing fork context in #1672 (commit 12da4851). | |
| stealth/space-bunny-alpha | — | closed not a bug | Closed as triaged after Python 3.14's default change from fork to forkserver was identified as the likely cause; explicitly using fork in chroot() was proposed. | |
| qwen/qwen3.6-35b-a3b | — | — | Closed after a contributor identified Python 3.14's default multiprocessing start method change to forkserver as the cause of chroot failures. Explicitly using the fork start method was proposed as the resolution. | |
| qwen/qwen3.6-35b-a3b |
Staleness:
15
Complexity:
25
Confidence:
95
Support Request:
0
|
keep open | Craft Parts chroot tests fail on Python 3.14 due to multiprocessing start method changes. Issue is triaged and a contributor has suggested switching to the fork start method. | |
| qwen/qwen3.6-35b-a3b |
Staleness:
20
Complexity:
45
Confidence:
85
Support Request:
10
|
keep open | Fails chroot tests on Python 3.14 due to multiprocessing start method changes. Triaged as a bug; a contributor suggested explicitly using the fork start method to resolve the Operation not permitted error. | |
| qwen/qwen3.6-35b-a3b |
Staleness:
10
Complexity:
45
Confidence:
90
Support Request:
5
|
keep open | Unit tests for chroot functionality fail on Python 3.14 due to multiprocessing changes, raising permission errors. The issue is triaged and awaiting investigation. | |
| qwen3.6-35b-a3b-mtp-q6 |
Staleness:
5
Complexity:
45
Confidence:
90
Support Request:
10
|
keep open | Reports test failures in Craft Parts when running on Python 3.14 due to multiprocessing changes. The issue is triaged and labeled as a bug, awaiting investigation and a fix. |
Update history
| Date | Change |
|---|---|
| closed | |
| updated |
Related work
-
Likely Fixed By:
canonical/craft-parts#1672
(confidence 100%)
Commit 12da4851 explicitly selects the fork multiprocessing context for chroot execution.
Related issues
| Issue | Project | State | Summary | Similarity |
|---|---|---|---|---|
| #1672 fix: use fork context for chroot in python 3.14 | craft-parts | merged | Merged PR canonical/craft-parts#1672 (commit 12da4851): chroot() now explicitly uses the multiprocessing 'fork' start context, restoring parent chroot state on Python 3.14. Closes #1642. |