Skip to content

Freeze the mapping a historic call is memorized with - #806

Closed
simpleqt wants to merge 2 commits into
pytest-dev:mainfrom
simpleqt:fix/call-historic-kwargs-copy
Closed

simpleqt wants to merge 2 commits into
pytest-dev:mainfrom
simpleqt:fix/call-historic-kwargs-copy

Conversation

@simpleqt

Copy link
Copy Markdown

Freeze the mapping a historic call is memorized with

HookCaller.call_historic() appends the kwargs mapping itself to
_call_history and replays it for every plugin registered afterwards. When the
hookspec declares no argument defaults, _apply_defaults() returns the caller's
own mapping, so the memorized call changes if the caller keeps using its mapping:

d = {"arg": "value-at-call-time"}
pm.hook.h.call_historic(kwargs=d)
d["arg"] = "mutated-after-the-call"
pm.register(LatePlugin())     # the late plugin sees "mutated-after-the-call"

The value was never passed to that hook call, and the result_callback is handed
the same mutated mapping. This is inconsistent with the case that does have a
hookspec default: _apply_defaults() merges into a new mapping there, so those
calls are already frozen today. Copying the mapping after the defaults are applied
makes both paths behave the same way and keeps the check, the already-performed
call and the memorized entry using one snapshot.

The regression test asserts that a plugin registered after the call, and the
result callback, both see the values the call was made with; it fails on main
(assert ['mutated-after-the-call'] == ['value-at-call-time']) and passes with the
change. testing/test_pluginmanager.py is 79 passed, the full suite 225 passed
before and 226 after, and ruff/mypy are clean at the pinned versions.

I found this while testing the public API — there is no issue to close.

AI assistance: this change was produced with AI assistance (an autonomous coding
agent), and the reproduction, red/green evidence and test run were executed
locally; a human reviewed the result before submission.

`HookCaller.call_historic()` keeps the ``kwargs`` mapping in
``_call_history`` and replays it for every plugin registered later. When the
hookspec declares no argument defaults, that mapping is the caller's own
object (``_apply_defaults()`` returns it unchanged), so mutating it after the
call silently rewrites the memorized call: plugins registered afterwards, and
the ``result_callback``, are handed values that were never passed to the hook
call. With a hookspec default the mapping is copied already, so the two paths
disagree.

Copy the mapping instead, so that a historic call is replayed with the values
it was made with.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

Copy link
Copy Markdown
Member

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

Closing. We don't accept PRs from unattended AI agents. This account opened 30+ PRs across unrelated projects today. The behavior is now tracked in #808.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants