[16.0][FIX] server_action_mass_edit: stop poisoning the registry on onchange#1292
Draft
DarioLodeiros wants to merge 1 commit into
Draft
[16.0][FIX] server_action_mass_edit: stop poisoning the registry on onchange#1292DarioLodeiros wants to merge 1 commit into
DarioLodeiros wants to merge 1 commit into
Conversation
``MassEditingWizard.onchange`` injects dynamic ``Selection`` fields into ``self._fields`` by assigning them directly into the dict. Because they never go through ``__set_name__``, the resulting ``Field`` instances have ``model_name=None`` and ``name=None``. Once a worker hits this code path, the next time any model is asked about its onchange dependencies (``Model._has_onchange`` → ``Registry.get_dependent_fields`` → ``Field.resolve_depends``) Odoo calls ``registry[self.model_name]``, which is ``registry[None]`` and raises ``KeyError: None``. The exception propagates back to the web client as a random RPC error on completely unrelated views (``bank_rec_widget``, ``knowledge.article``, etc.). To make matters worse, the cleanup ``for field in dynamic_fields: self._fields.pop(field)`` is not wrapped in ``try/finally``, so any exception inside ``super().onchange()`` leaves the orphan Fields behind in the class-level ``_fields`` dict. The worker stays poisoned until the process is restarted. This patch: * Calls ``Field.__set_name__`` on each dynamic Field so it is bound to the owner class (``model_name`` and ``name`` are populated). The registry lookup then resolves to ``mass.editing.wizard`` as expected. * Wraps the ``super().onchange()`` call in ``try/finally`` so the dynamic fields are always removed from ``_fields``, even when the parent ``onchange`` raises. A regression test asserts both invariants: the dynamic Field has the correct ``model_name`` during the parent ``onchange`` call, and ``_fields`` is restored to its original state on both success and failure paths.
This was referenced May 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
In production we have been seeing random
KeyError: Nonetraces every few minutes, fired from unrelated views (bank_rec_widget,knowledge.article, ...) and surfaced to the web client asRPC_ERROR:The trace never points at this module on the surface, but the only thing that has been planting
Fieldinstances withmodel_name = Noneinto the registry isMassEditingWizard.onchange.Root cause
MassEditingWizard.onchangeinjects dynamicSelectionfields by assigning them straight intoself._fields:These
Fieldobjects never go through__set_name__, somodel_nameandnamestay at the defaultNone. Any later access toregistry._field_triggers— for example from_has_onchangewhile building a view for any other model — callsfield.resolve_depends(self)→registry[None]→KeyError: None.The
for field in dynamic_fields: self._fields.pop(field)cleanup is also not wrapped intry/finally. If anything insidesuper().onchange()raises, the orphanFieldinstances are left inmass.editing.wizard._fields(a class-level dict, shared by every request on that worker). The worker stays poisoned for the rest of its lifetime: every request that touches_field_triggersthen explodes randomly on completely unrelated views.I could not find an open issue/PR covering this exact scenario in OCA/server-ux. PR #1203 deals with the view cache mechanism on 17.0+, which is unrelated.
Fix
Field.__set_name__on each dynamic field before inserting it into_fields, somodel_name/nameare populated and registry lookups resolve tomass.editing.wizardas expected.super().onchange()call intry/finally, so the dynamic fields are always removed from_fields— even when the parentonchangeraises.Regression test
test_onchange_dynamic_fields_are_bound_and_cleanedasserts both invariants:super().onchange()is running, each dynamic Field hasmodel_name == 'mass.editing.wizard'(without the fix the test fails withNone != 'mass.editing.wizard': Dynamic field selection__bank_ids was injected with model_name=None)._fieldsis restored to its original state on both the success path and a failure path that forcessuper().onchange()to raise.Affected versions
The same pattern is present on 17.0, 18.0 and the 19.0 migration PRs. I will open companion PRs once this one is reviewed.