Skip to content

[19.0] env.user empty during flush/recompute inside queue jobs #922

Closed

Description

@dannyadair

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:

  1. 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()
  1. Enqueue a write that causes a flush: partner.with_delay().touch()
  2. 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 = env directly.
  • 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

  1. sbidoul commented on Apr 22, 2026

    @sbidoul
    Member

    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?

  2. simahawk commented on Apr 22, 2026

    @simahawk
    Contributor

    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?

    Indeed, it would make sense.

  3. dannyadair commented on Apr 22, 2026

    @dannyadair
    ContributorAuthor

    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.env around job.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.env swap as a follow-up PR if you agree that's the right direction. For now, we needed to stop the errors.

  4. added 4 commits that reference this issue on Jun 29, 2026
    415420f
    588919a
    eeea3ee
    fd214cb
  5. MiquelRForgeFlow commented on Jul 20, 2026

    @MiquelRForgeFlow
    Contributor

    Issue can be closed.

  6. sbidoul commented on Jul 20, 2026

    @sbidoul
    Member

    Done in #923

  7. added 4 commits that reference this issue on Sep 22, 2026
    2564106
    5c1fc71
    0ea0118
    d9dcf24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions