Skip to content

[19.0] fs_attachment: use_as_default_for_attachments silently dropped when set in fs.storage.create() — _storage() reverts to 'file' in fresh processes #657

Description

@panayiotiska

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.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions