Module
fs_attachment 19.0.1.1.2 / fs_storage 19.0.1.1.2 (also affects 18.0 by code inspection)
Describe the bug
Passing use_as_default_for_attachments=True to fs.storage.create() silently loses the flag: it reads back True inside the creating transaction, but the value is never persisted to the server_env_defaults JSON column, so any fresh registry/process computes it as False. As a result ir.attachment._storage() silently falls back to "file" even though the storage appeared correctly configured when it was created.
A subsequent write({"use_as_default_for_attachments": True}) on the same record persists the key correctly (x_use_as_default_for_attachments_env_default appears in server_env_defaults), so the bug is specific to the create path.
To Reproduce
# process 1 (e.g. odoo shell)
storage = env["fs.storage"].create({
"name": "S3 attachments",
"code": "s3_attachments",
"protocol": "s3",
"directory_path": "my-bucket/attachments",
"options": '{"client_kwargs": {"region_name": "eu-central-1"}}',
"use_as_default_for_attachments": True,
})
env.cr.commit()
storage.use_as_default_for_attachments # True (in-transaction cache)
env["ir.attachment"]._storage() # "s3_attachments" — looks fine
# check what was actually persisted
env.cr.execute("SELECT server_env_defaults FROM fs_storage WHERE id=%s", (storage.id,))
# -> x_protocol_env_default, x_directory_path_env_default, x_options_env_default ...
# are all present, but x_use_as_default_for_attachments_env_default is MISSING
# process 2 (new odoo shell / worker restart)
env["ir.attachment"]._storage() # "file" <-- flag silently lost
Note fs.storage.get_storage_code_for_attachments_fallback() is @ormcached, which can mask or delay the observation on a long-running server; a fresh process shows it deterministically.
Expected behavior
Either the flag persists like every other _server_env_fields entry set at create time (all the sibling values in the same create() call do persist), or create() raises so the misconfiguration is visible.
Why the silent failure is dangerous
ir.attachment.force_storage() is guarded (location not in self._get_storage_codes() delegates to core), so the public API degrades gracefully. But any code path that reaches _force_storage_to_object_storage() while _storage() has silently reverted to "file" builds the migration domain against "file", "moves" every classic filestore attachment onto its own path, and then clean_fs() deletes those paths — i.e. it deletes the live filestore content. We reproduced exactly this on a dev database while evaluating the module (recovered from a filesystem backup taken beforehand; no data lost). The internal method is admittedly private, but the create() bug is what turns a config that looked verified into a "file" fallback later, so the two together make a nasty trap.
Suggested hardening independent of the fix: _force_storage_to_object_storage() could assert the resolved storage is an fs.storage code before building the domain.
Workaround
Set the flag in a second step and verify from a fresh process:
storage = env["fs.storage"].create({...}) # without the flag
storage.write({"use_as_default_for_attachments": True})
env.cr.commit()
# then, from a NEW process:
assert env["ir.attachment"]._storage() == "s3_attachments"
Or configure the storage entirely via server_environment config ([fs_storage.<code>] section), which bypasses the default-field persistence entirely.
Additional context
Odoo 19.0, server_environment 19.0 from OCA/server-env, running_env unset (defaults to test) — also reproduced with the record created via plain ORM in odoo shell. Happy to provide more detail or test a patch.
Module
fs_attachment 19.0.1.1.2 / fs_storage 19.0.1.1.2 (also affects 18.0 by code inspection)
Describe the bug
Passing
use_as_default_for_attachments=Truetofs.storage.create()silently loses the flag: it reads backTrueinside the creating transaction, but the value is never persisted to theserver_env_defaultsJSON column, so any fresh registry/process computes it asFalse. As a resultir.attachment._storage()silently falls back to"file"even though the storage appeared correctly configured when it was created.A subsequent
write({"use_as_default_for_attachments": True})on the same record persists the key correctly (x_use_as_default_for_attachments_env_defaultappears inserver_env_defaults), so the bug is specific to the create path.To Reproduce
Note
fs.storage.get_storage_code_for_attachments_fallback()is@ormcached, which can mask or delay the observation on a long-running server; a fresh process shows it deterministically.Expected behavior
Either the flag persists like every other
_server_env_fieldsentry set at create time (all the sibling values in the samecreate()call do persist), orcreate()raises so the misconfiguration is visible.Why the silent failure is dangerous
ir.attachment.force_storage()is guarded (location not in self._get_storage_codes()delegates to core), so the public API degrades gracefully. But any code path that reaches_force_storage_to_object_storage()while_storage()has silently reverted to"file"builds the migration domain against"file", "moves" every classic filestore attachment onto its own path, and thenclean_fs()deletes those paths — i.e. it deletes the live filestore content. We reproduced exactly this on a dev database while evaluating the module (recovered from a filesystem backup taken beforehand; no data lost). The internal method is admittedly private, but the create() bug is what turns a config that looked verified into a"file"fallback later, so the two together make a nasty trap.Suggested hardening independent of the fix:
_force_storage_to_object_storage()could assert the resolved storage is an fs.storage code before building the domain.Workaround
Set the flag in a second step and verify from a fresh process:
Or configure the storage entirely via server_environment config (
[fs_storage.<code>]section), which bypasses the default-field persistence entirely.Additional context
Odoo 19.0,
server_environment19.0 from OCA/server-env,running_envunset (defaults totest) — also reproduced with the record created via plain ORM inodoo shell. Happy to provide more detail or test a patch.