AlmaLinux unit setup exposed a leaked InstanceTimer thread exception; cleanup was later fixed by #996, leaving this open issue apparently resolved.
Suggested action: close resolved
Reason: The traceback is collected as an unhandled exception from InstanceTimer, pointing to a background-thread lifecycle problem. Commit 78367c2, merged as canonical/craft-providers#996, explicitly fixes stopping the instance when base_configuration.setup() raises an error and directly addresses this failure mode.
### Bug Description
We got an error when setting up an almalinux test: https://github.com/canonical/craft-providers/actions/runs/19582107478/job/56082351892?pr=852
### To Reproduce
https://github.com/canonical/craft-providers/actions/runs/19582107478/job/56082351892?pr=852
### part yaml
```shell
```
### Relevant log output
```shell
025-11-21T20:26:27.1887245Z _ ERROR at setup of test_setup[None-almalinux-base-v7-packages1-expected_packages1-None-expected_snap_call0-False-environment1-https_proxy=http://foo.bar:8081\nPATH=/snap\nhttp_proxy=http://foo.bar:8080\n-AlmaLinuxBaseAlias.NINE] _
2025-11-21T20:26:27.1888392Z
2025-11-21T20:26:27.1888588Z thread = <InstanceTimer(Thread-9, stopped daemon 139927579784768)>
2025-11-21T20:26:27.1889633Z
2025-11-21T20:26:27.1889752Z def invoke_excepthook(thread):
2025-11-21T20:26:27.1890049Z global excepthook
2025-11-21T20:26:27.1890279Z try:
2025-11-21T20:26:27.1890481Z hook = excepthook
2025-11-21T20:26:27.1890734Z if hook is None:
2025-11-21T20:26:27.1890986Z hook = old_excepthook
2025-11-21T20:26:27.1891247Z
2025-11-21T20:26:27.1891479Z args = ExceptHookArgs([*sys_exc_info(), thread])
2025-11-21T20:26:27.1891789Z
2025-11-21T20:26:27.1891965Z > hook(args)
2025-11-21T20:26:27.1892109Z
2025-11-21T20:26:27.1892227Z /usr/lib/python3.10/threading.py:1326:
2025-11-21T20:26:27.1892564Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2025-11-21T20:26:27.1893346Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/threadexception.py:111: in thread_exception_hook
2025-11-21T20:26:27.1894141Z tracemalloc_tb = "\n" + tracemalloc_message(args.thread)
2025-11-21T20:26:27.1894540Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2025-11-21T20:26:27.1894776Z
2025-11-21T20:26:27.1894967Z source = <InstanceTimer(Thread-9, stopped daemon 139927579784768)>
2025-11-21T20:26:27.1895257Z
2025-11-21T20:26:27.1895400Z def tracemalloc_message(source: object) -> str:
2025-11-21T20:26:27.1895687Z if source is None:
2025-11-21T20:26:27.1895898Z return ""
2025-11-21T20:26:27.1896075Z
2025-11-21T20:26:27.1896229Z try:
2025-11-21T20:26:27.1896739Z > import tracemalloc
2025-11-21T20:26:27.1896983Z E KeyError: 'tracemalloc'
2025-11-21T20:26:27.1897151Z
2025-11-21T20:26:27.1897531Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/tracemalloc.py:9: KeyError
2025-11-21T20:26:27.1897992Z
2025-11-21T20:26:27.1898163Z The above exception was the direct cause of the following exception:
2025-11-21T20:26:27.1898429Z
2025-11-21T20:26:27.1898534Z cls = <class '_pytest.runner.CallInfo'>
2025-11-21T20:26:27.1899103Z func = <function call_and_report.<locals>.<lambda> at 0x7f436dce3be0>
2025-11-21T20:26:27.1899580Z when = 'setup'
2025-11-21T20:26:27.1899880Z reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)
2025-11-21T20:26:27.1900146Z
2025-11-21T20:26:27.1900226Z @classmethod
2025-11-21T20:26:27.1900406Z def from_call(
2025-11-21T20:26:27.1900584Z cls,
2025-11-21T20:26:27.1900768Z func: Callable[[], TResult],
2025-11-21T20:26:27.1901071Z when: Literal["collect", "setup", "call", "teardown"],
2025-11-21T20:26:27.1901485Z reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
2025-11-21T20:26:27.1901861Z ) -> CallInfo[TResult]:
2025-11-21T20:26:27.1902118Z """Call func, wrapping the result in a CallInfo.
2025-11-21T20:26:27.1902379Z
2025-11-21T20:26:27.1902546Z :param func:
2025-11-21T20:26:27.1902785Z The function to call. Called without arguments.
2025-11-21T20:26:27.1903115Z :type func: Callable[[], _pytest.runner.TResult]
2025-11-21T20:26:27.1903392Z :param when:
2025-11-21T20:26:27.1903618Z The phase in which the function is called.
2025-11-21T20:26:27.1903879Z :param reraise:
2025-11-21T20:26:27.1904169Z Exception or exceptions that shall propagate if raised by the
2025-11-21T20:26:27.1904556Z function, instead of being wrapped in the CallInfo.
2025-11-21T20:26:27.1904828Z """
2025-11-21T20:26:27.1905004Z excinfo = None
2025-11-21T20:26:27.1905212Z instant = timing.Instant()
2025-11-21T20:26:27.1905428Z try:
2025-11-21T20:26:27.1905615Z > result: TResult | None = func()
2025-11-21T20:26:27.1905783Z
2025-11-21T20:26:27.1906123Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/runner.py:344:
2025-11-21T20:26:27.1906646Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2025-11-21T20:26:27.1907373Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/runner.py:246: in <lambda>
2025-11-21T20:26:27.1907989Z lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
2025-11-21T20:26:27.1908596Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/pluggy/_hooks.py:512: in __call__
2025-11-21T20:26:27.1909349Z return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
2025-11-21T20:26:27.1909991Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/pluggy/_manager.py:120: in _hookexec
2025-11-21T20:26:27.1910604Z return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
2025-11-21T20:26:27.1911439Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/logging.py:843: in pytest_runtest_setup
2025-11-21T20:26:27.1911980Z yield
2025-11-21T20:26:27.1912470Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/capture.py:895: in pytest_runtest_setup
2025-11-21T20:26:27.1913017Z return (yield)
2025-11-21T20:26:27.1913546Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/threadexception.py:142: in pytest_runtest_setup
2025-11-21T20:26:27.1914141Z collect_thread_exception(item.config)
2025-11-21T20:26:27.1914423Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2025-11-21T20:26:27.1914625Z
2025-11-21T20:26:27.1914917Z config = <_pytest.config.Config object at 0x7f43714cdc00>
2025-11-21T20:26:27.1915138Z
2025-11-21T20:26:27.1915276Z def collect_thread_exception(config: Config) -> None:
2025-11-21T20:26:27.1915629Z pop_thread_exception = config.stash[thread_exceptions].pop
2025-11-21T20:26:27.1916091Z errors: list[pytest.PytestUnhandledThreadExceptionWarning | RuntimeError] = []
2025-11-21T20:26:27.1916484Z meta = None
2025-11-21T20:26:27.1916668Z hook_error = None
2025-11-21T20:26:27.1916860Z try:
2025-11-21T20:26:27.1917038Z while True:
2025-11-21T20:26:27.1917223Z try:
2025-11-21T20:26:27.1917436Z meta = pop_thread_exception()
2025-11-21T20:26:27.1917704Z except IndexError:
2025-11-21T20:26:27.1917925Z break
2025-11-21T20:26:27.1918105Z
2025-11-21T20:26:27.1918297Z if isinstance(meta, BaseException):
2025-11-21T20:26:27.1918641Z hook_error = RuntimeError("Failed to process thread exception")
2025-11-21T20:26:27.1919091Z hook_error.__cause__ = meta
2025-11-21T20:26:27.1919358Z errors.append(hook_error)
2025-11-21T20:26:27.1919603Z continue
2025-11-21T20:26:27.1919792Z
2025-11-21T20:26:27.1919955Z msg = meta.msg
2025-11-21T20:26:27.1920164Z try:
2025-11-21T20:26:27.1920468Z warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
2025-11-21T20:26:27.1920907Z except pytest.PytestUnhandledThreadExceptionWarning as e:
2025-11-21T20:26:27.1921489Z # This except happens when the warning is treated as an error (e.g. `-Werror`).
2025-11-21T20:26:27.1921869Z if meta.exc_value is not None:
2025-11-21T20:26:27.1922180Z # Exceptions have a better way to show the traceback, but
2025-11-21T20:26:27.1922542Z # warnings do not, so hide the traceback from the msg and
2025-11-21T20:26:27.1922906Z # set the cause so the traceback shows up in the right place.
2025-11-21T20:26:27.1923229Z e.args = (meta.cause_msg,)
2025-11-21T20:26:27.1923486Z e.__cause__ = meta.exc_value
2025-11-21T20:26:27.1923742Z errors.append(e)
2025-11-21T20:26:27.1923961Z
2025-11-21T20:26:27.1924280Z if len(errors) == 1:
2025-11-21T20:26:27.1924506Z > raise errors[0]
2025-11-21T20:26:27.1924770Z E RuntimeError: Failed to process thread exception
2025-11-21T20:26:27.1924981Z
2025-11-21T20:26:27.1925374Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/threadexception.py:70: RuntimeError
2025-11-21T20:26:27.1925962Z =============================== warnings summary ===============================
2025-11-21T20:26:27.1926318Z .venv/lib/python3.10/site-packages/_pytest/fixtures.py:1392
2025-11-21T20:26:27.1927106Z /home/runner/work/craft-providers/craft-providers/.venv/lib/python3.10/site-packages/_pytest/fixtures.py:1392: PytestRemovedIn9Warning: Marks applied to fixtures have no effect
2025-11-21T20:26:27.1928028Z See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function
2025-11-21T20:26:27.1928494Z return fixture_marker(fixture_function)
2025-11-21T20:26:27.1928684Z
2025-11-21T20:26:27.1928830Z tests/unit/lxd/test_lxd_instance.py::test_launch
2025-11-21T20:26:27.1930033Z /home/runner/work/craft-providers/craft-providers/tests/unit/lxd/test_lxd_instance.py:580: UserWarning: Application configured to intercept guest mknod calls, but the host OS does not support intercepting mknod.
2025-11-21T20:26:27.1930824Z instance.launch(
2025-11-21T20:26:27.1930949Z
2025-11-21T20:26:27.1931095Z tests/unit/lxd/test_lxd_instance.py::test_launch_all_opts
2025-11-21T20:26:27.1932127Z /home/runner/work/craft-providers/craft-providers/tests/unit/lxd/test_lxd_instance.py:601: UserWarning: Application configured to intercept guest mknod calls, but the host OS does not support intercepting mknod.
2025-11-21T20:26:27.1932923Z instance.launch(
2025-11-21T20:26:27.1933040Z
2025-11-21T20:26:27.1933224Z -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
2025-11-21T20:26:27.1933613Z =========================== short test summary info ============================
2025-11-21T20:26:27.1934787Z ERROR tests/unit/bases/test_almalinux.py::test_setup[None-almalinux-base-v7-packages1-expected_packages1-None-expected_snap_call0-False-environment1-https_proxy=http://foo.bar:8081\nPATH=/snap\nhttp_proxy=http://foo.bar:8080\n-AlmaLinuxBaseAlias.NINE] - RuntimeError: Failed to process thread exception
2025-11-21T20:26:27.1936034Z = 1623 passed, 10 skipped, 261 deselected, 3 warnings, 1 error in 1183.99s (0:19:43) =
```
Evaluation history
Date
Model
Scores
Action
Summary
stealth/space-bunny-alpha
Impact:
38
Quick Win:
19
Complexity:
44
Confidence:
90
Actionability:
87
close resolved
AlmaLinux unit setup exposed a leaked InstanceTimer thread exception; cleanup was later fixed by #996, leaving this open issue apparently resolved.
stealth/space-bunny-alpha
Impact:
32
Quick Win:
10
Complexity:
55
Confidence:
86
Actionability:
68
keep open
Open maintainer-filed bug: AlmaLinux unit-test setup intermittently errors when an InstanceTimer thread fails; pytest reports a tracemalloc import KeyError rather than an AlmaLinux failure.
qwen3.6-35b-a3b-mtp-q6
Impact:
20
Quick Win:
14
Staleness:
80
Complexity:
30
Confidence:
50
Support Request:
20
close stale
Test setup failure in almalinux test due to pytest internal KeyError: 'tracemalloc'. Issue is 273 days old with no maintainer response or assignee.
qwen/qwen3.6-35b-a3b
Staleness:
75
Complexity:
30
Confidence:
85
Support Request:
0
needs triage
Test setup failure for AlmaLinux causing a pytest thread exception. Issue remains open with no maintainer comments or updates for over 90 days.
qwen3.6-35b-a3b-mtp-q6
Staleness:
80
Complexity:
15
Confidence:
85
Support Request:
5
close stale
CI test failure in test_almalinux.py caused by an internal pytest KeyError on tracemalloc. No maintainer response or comments in over 80 days.