Skip to content

[19.0][FIX] queue_job: repair default_env in runjob#947

Closed
Ricardoalso wants to merge 1 commit into
OCA:19.0from
camptocamp:fix-runjob-default-env-auth-none
Closed

[19.0][FIX] queue_job: repair default_env in runjob#947
Ricardoalso wants to merge 1 commit into
OCA:19.0from
camptocamp:fix-runjob-default-env-auth-none

Conversation

@Ricardoalso

Copy link
Copy Markdown

IrHttp._auth_method_none() forces transaction.default_env to an environment with uid=None for every auth="none" route, including /queue_job/runjob. Any env.user access during a savepoint-triggered flush inside a job (e.g. a recompute/constrain calling has_group()) then raises "Expected singleton: res.users()", since browse(None) is an empty recordset.

Repair default_env to the real job-running user right after it is acquired, so any deferred computation flushed during the job's execution uses a valid environment.

Problem

Jobs executed through the built-in /queue_job/runjob HTTP endpoint can
intermittently — but for some jobs, deterministically — fail with:

ValueError: Expected singleton: res.users()

raised from deep inside unrelated code (a has_group() call, a computed
field, any @api.constrains method — anywhere self.env.user is accessed).
The job's own code is correct; nothing in the job itself touches env.user
incorrectly. The failure happens later, during an unrelated flush.

Root cause

/queue_job/runjob is declared auth="none". For every auth="none"
route, Odoo's IrHttp._auth_method_none() runs before the controller body,
and does this unconditionally:

request.env = api.Environment(request.env.cr, None, request.env.context)
request.env.transaction.default_env = request.env

This pins the transaction's default_env to an environment with
uid=None, bypassing the truthy-uid guard that normally protects
Transaction.default_env assignment (Environment.__new__,
odoo/orm/environments.py).

runjob() then builds its own, correctly-scoped environment
(env = http.request.env(user=SUPERUSER_ID)) and runs the job with it —
but this is a different Environment object. It does not touch
transaction.default_env, which stays pinned to the uid=None one set by
_auth_method_none().

The crash surfaces later because Cursor.flush() always delegates to the
transaction's default_env, not to whichever environment happened to
schedule the pending computation:

def flush(self) -> None:
    if self.default_env is not None:
        self.default_env.flush_all()
    ...

So any deferred field computation — a stored compute, an
@api.constrains method, anything reached through the generic
_compute_field_value_validate_fields chain — that is still pending
when a with cr.savepoint(): block inside the job exits (which triggers an
implicit flush) executes against the broken uid=None environment. If that
code path happens to read self.env.user, env.user resolves to
browse(None), an empty recordset — not a missing/inactive user, an
actually empty one — and ensure_one() (e.g. inside has_group()) raises.

Whether a given job reproduces this depends entirely on whether some
computation happens to still be pending exactly at that savepoint-exit
flush.

`IrHttp._auth_method_none()` forces `transaction.default_env` to an
environment with `uid=None` for every `auth="none"` route, including
`/queue_job/runjob`. Any `env.user` access during a savepoint-triggered
flush inside a job (e.g. a recompute/constrain calling `has_group()`)
then raises "Expected singleton: res.users()", since `browse(None)`
is an empty recordset.

Repair `default_env` to the real job-running user right after it is
acquired, so any deferred computation flushed during the job's
execution uses a valid environment.
@OCA-git-bot

Copy link
Copy Markdown
Contributor

Hi @guewen, @sbidoul,
some modules you are maintaining are being modified, check this out!

@Ricardoalso Ricardoalso changed the title [FIX] queue_job: repair default_env in runjob [19.0][FIX] queue_job: repair default_env in runjob Jul 15, 2026
@sbidoul

sbidoul commented Jul 15, 2026

Copy link
Copy Markdown
Member

Duplicate of #923 ?

@Ricardoalso

Copy link
Copy Markdown
Author

Duplicate of #923 ?

I missed that one ! Thanks for pointing it out @sbidoul

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants