Skip to content

Require pluggy>=1.6 so caplog keeps its handler (#9236) - #15128

Merged
RonnyPfannschmidt merged 1 commit into
pytest-dev:mainfrom
RonnyPfannschmidt:fix-9236-pluggy-floor
Oct 17, 2026
Merged

RonnyPfannschmidt merged 1 commit into
pytest-dev:mainfrom
RonnyPfannschmidt:fix-9236-pluggy-floor

Conversation

@RonnyPfannschmidt

Copy link
Copy Markdown
Member

Written by Claude Opus 5.5 via Claude Code for the pytest maintainers; I prompted it, it did the work, I read it.

Closes #9236.

#9236 is not a garbage-collected handler, and #11123 did not fix it. It is a pluggy bug that pluggy 1.6.0 fixed (pytest-dev/pluggy#546), and it is still reachable because pytest allows pluggy>=1.5.

Mechanism

  • pluggy < 1.6 re-raises out of the teardown loop in _multicall when an old-style hookwrapper raises in its own teardown. Every remaining teardown is skipped.
  • The logging plugin's pytest_runtest_call wrapper is one of them. It stays suspended inside _runtest_for, still holding its entered catching_logs contexts, kept alive by a cycle through the exception traceback.
  • When gc finalises it, catching_logs.__exit__ removes caplog_handler from the root logger. There is one handler per plugin, so that is the same object the next test's caplog just attached, and that test records nothing.

The reproducer from the issue still fails on current main with pluggy 1.5.0 and passes with 1.6.0. Bisecting pluggy points to pytest-dev/pluggy#546.

Change

  • pluggy>=1.6,<2 in pyproject.toml and uv.lock. pluggy 1.6 needs Python >= 3.9, so there is no conflict with our >= 3.10.
  • A regression test in testing/logging/test_fixture.py. It fails on pluggy 1.5.0 and passes on 1.6.0. It runs in a subprocess because in-process the HookRecorder keeps the failing call's CallInfo, whose traceback keeps the suspended wrapper alive, so the bug never shows.

@pyba-chronographer pyba-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Oct 4, 2026
pluggy < 1.6 stopped running the remaining hook wrapper teardowns once an
old-style hookwrapper raised in its own teardown. The logging plugin's
pytest_runtest_call wrapper stayed suspended inside catching_logs, held
by a reference cycle through the exception traceback. When gc finalised
it, the stale catching_logs.__exit__ removed the plugin's shared
LogCaptureHandler from the root logger -- the same handler the next
test's caplog had just attached -- so that test recorded nothing.

pytest-dev/pluggy#546 (released in 1.6.0) runs every teardown, so the
wrapper always unwinds in place.

The regression test runs in a subprocess: in-process, the HookRecorder
keeps the failing call's CallInfo alive, the wrapper is never finalised,
and the test passes on pluggy 1.5 as well.

Closes pytest-dev#9236

Co-Authored-By: Claude Opus 5.5 via Claude Code <noreply@anthropic.com>
@RonnyPfannschmidt
RonnyPfannschmidt merged commit 420cf37 into pytest-dev:main Oct 17, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LogCaptureHandler can be garbage collected when caplog fixture is in use

2 participants