Repository navigation
[19.0] env.user empty during flush/recompute inside queue jobs #922
Description
Module
queue_job
Describe the bug
https://github.com/OCA/queue/blob/19.0/queue_job/controllers/main.py#L218
env = http.request.env(user=SUPERUSER_ID)
I believe needs to be
http.request.update_env(user=SUPERUSER_ID)
env = http.request.env
runjob is declared as auth=none.
https://github.com/OCA/queue/blob/19.0/queue_job/controllers/main.py#L212
_auth_method_none forces uid=None for request env and default_env
https://github.com/odoo/odoo/blob/19.0/odoo/addons/base/models/ir_http.py#L263
So env = http.request.env(user=SUPERUSER_ID)
only produces a local env with that superuser, the other two are still None.
The job "itself" runs fine as the specified user, but any flush/recompute on a stored field uses the default_env with uid None.
We see a singleton error on is_backend_user = self.env.user.has_group('base.group_user') (in Odoo Enterprise compute_signing_user) because self.env.user is an empty recordset.
File ".../odoo/orm/environments.py", line 592, in flush
self.default_env.flush_all()
...
File ".../enterprise/account_accountant/models/account_move.py", line 76, in _compute_signing_user
is_backend_user = self.env.user.has_group('base.group_user')
File ".../odoo/addons/base/models/res_users.py", line 1074, in has_group
self.ensure_one()
ValueError: Expected singleton: res.users()
The default_env mechanism was introduced 2 years ago
odoo/odoo@897e2f2
but the flush() was tolerant. Ironically,
odoo/odoo@45e1c05
(Jan 24 2025) states "flush as public user when there is no default_env"
but now _auth_method_none always sets default_env
odoo/odoo@45e1c05#diff-c5500b83255cdef56ed07cb5a74e9b0899ad378cf9a3739d9676101191c83eb2
Using update_env repoints the default_env correctly
https://github.com/odoo/odoo/blob/19.0/odoo/http.py#L1836
To Reproduce
Affected versions: 19.0
Steps to reproduce the behavior:
- Create a module with a computed attribute that accesses
self.env.user(I added an explicit flush to force the pending compute):
class ResPartner(models.Model):
_inherit = "res.partner"
x_toucher = fields.Many2one("res.users", store=True, compute="_c")
@api.depends("name")
def _c(self):
self.env.user.has_group("base.group_user") # ensure_one() fails on empty recordset
for r in self:
r.x_toucher = self.env.user
def touch(self):
self.write({"name": "x"})
self.env.cr.flush() # forces Transaction.flush() -> default_env.flush_all()
- Enqueue a write that causes a flush:
partner.with_delay().touch() - Run the jobrunner -> job fails with Expected singleton: res.users().
Expected behavior
self.env.user inside stored-field recomputes triggered during a queue job should resolve to a real user (the job's user_id or SUPERUSER), not an empty recordset.
Additional context
In the same commit (45e1c05), core updated the two other places that create a uid=None env to set default_env explicitly:
- odoo/service/common.py::exp_authenticate assigns
env.transaction.default_env = envdirectly. - addons/bus/websocket.py added a
new_env()helper that does the same.
Neither can use Request.update_env because they don't run inside an HTTP Request.
queue_job/controllers/main.py::runjob does run inside a Request, so the idiomatic fix is http.request.update_env(user=SUPERUSER_ID), which has been repointing default_env since 897e2f2.
Activity
sbidoul commentedon Apr 22, 2026
Interesting. I wonder if that is not revealing that the flush is done using the wrong env. Should the flush not be done using the same env/user that is actually running the job?
simahawk commentedon Apr 22, 2026
Indeed, it would make sense.
dannyadair commentedon Apr 22, 2026
Good point. The SUPERUSER_ID is there because the controller itself needs bookkeeping privileges - acquire the job, lock the row, write state, commit. But job.perform() runs under the job's originally-enqueued user (from _load_from_db_record via JobDecoder). Recomputes triggered by that should also run as that user, not escalated to SUPERUSER.
I think the cleanest split would be: controller uses SUPERUSER for bookkeeping, but _try_perform_job temporarily swaps
transaction.default_env = job.envaroundjob.perform()so any auto-flush during the job's own work recomputes under the job's user. Restore to the SUPERUSER env for the subsequent set_done / store / commit.I kept this PR minimal because it's a strict improvement over the current (user=SUPERUSER_ID) call - which was aiming at what update_env actually does.
Happy to do the
default_env = job.envswap as a follow-up PR if you agree that's the right direction. For now, we needed to stop the errors.[FIX] queue_job: repoint default_env to SUPERUSER in runjob
[ADD] queue_job: HttpCase regression tests for /queue_job/runjob
[ADD] queue_job: HttpCase regression tests for /queue_job/runjob
[ADD] queue_job: HttpCase regression tests for /queue_job/runjob
MiquelRForgeFlow commentedon Jul 20, 2026
Issue can be closed.
sbidoul commentedon Jul 20, 2026
Done in #923
[FIX] queue_job: repoint default_env to SUPERUSER in runjob
[ADD] queue_job: HttpCase regression tests for /queue_job/runjob
[FIX] queue_job: repoint default_env to SUPERUSER in runjob
[ADD] queue_job: HttpCase regression tests for /queue_job/runjob