Skip to content

[16.0][FIX] fs_attachment: reconcile fs_filename when datas/raw is rewritten#598

Open
TecnologiaIG wants to merge 1 commit intoOCA:16.0from
TecnologiaIG:16.0-fs_attachment-write-guard-datas
Open

[16.0][FIX] fs_attachment: reconcile fs_filename when datas/raw is rewritten#598
TecnologiaIG wants to merge 1 commit intoOCA:16.0from
TecnologiaIG:16.0-fs_attachment-write-guard-datas

Conversation

@TecnologiaIG
Copy link
Copy Markdown

Problem

ir.attachment.write only called _enforce_meaningful_storage_filename when name was in vals. But every other code path that rewrites an attachment's bytes without touching its name — asset-bundle regeneration, ORM image field updates, inline kanban image writes, the _force_storage migration itself — replaces store_fname via _storage_file_write but leaves fs_filename / fs_storage_code / fs_storage_id stale.

A NULL fs_filename defeats the gate in IrBinary._get_fs_attachment_for_field:

def _get_fs_attachment_for_field(self, record, field_name):
    if record._name == "ir.attachment" and record.fs_filename:  # ← NULL → None
        return record
    ...
    if fs_attachment and fs_attachment.fs_filename:  # ← same gate
        return fs_attachment
    return None

With None returned, base Odoo falls through to Stream.from_attachment, which takes the raw store_fname (e.g. "azure_attachments://<hash>") and passes it straight to os.stat — raising FileNotFoundError → 500 on every /web/image/<res_model>/<id>/<field> call that touches the record.

Why it was latent for most installations

When the default storage is the plain filestore, store_fname is just a 40-char hash — the same format _enforce_meaningful_storage_filename would rename it to. Re-reads work whether or not fs_filename is set. The mismatch only becomes fatal once store_fname takes the <storage_code>://… form, because base Odoo cannot parse that path.

Observed incident (2026-04-20 → 2026-04-22)

A production Odoo.sh instance flipped fs.storage.use_as_default_for_attachments to an abfs (Azure Blob) backend. The deploy that followed regenerated thousands of asset bundles and ran background image updates, every one via write({"datas": ...}). 36,804 ir.attachment rows entered the broken state in under 48 h. Every /web/image hit for those records returned 500 — assets included — so the webclient rendered as a blank page in every browser.

Recovery required an out-of-band UPDATE + filestore restore from the pre-deploy backup. The hash-to-filestore path mapping preserved the data, but the fs_filename mismatch alone was enough to take the site down.

Fix

-        if "name" in vals:
+        if "name" in vals or "datas" in vals or "raw" in vals:
             self._enforce_meaningful_storage_filename()

_enforce_meaningful_storage_filename is idempotent and already no-ops when nothing needs to change — the broader guard has no downside for the existing "name" path.

Test plan

  • Production: the fix is deployed on the affected instance; no new records entered the broken state since 2026-04-22 05:30 GT.
  • Staging reproduction: attachment.write({"datas": new_bytes}) on a record living in an fs.storage backend → assert fs_filename is non-null afterwards.
  • Asset-bundle regeneration: force a regeneration (e.g. debug=assets on a clean DB) → confirm the resulting ir.attachment rows have fs_filename populated.

Stacks on

Version bumped to 16.0.2.0.4 to leave room for #596 (16.0.2.0.2, cursor timeouts) and #597 (16.0.2.0.3, GC batching/infinite-loop). Happy to rebase as needed.

Forward-ports

The affected write override is identical on 17.0 / 18.0 / 19.0 as of today's tip; happy to forward-port once 16.0 is reviewed.

@OCA-git-bot
Copy link
Copy Markdown
Contributor

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

``ir.attachment.write`` only called ``_enforce_meaningful_storage_filename``
when ``name`` was in ``vals``. Every other code path that rewrites
attachment bytes without touching the name — asset-bundle regeneration,
ORM image field updates, inline kanban image writes, the
``_force_storage`` migration itself — replaces ``store_fname`` via
``_storage_file_write`` but leaves ``fs_filename`` / ``fs_storage_code``
/ ``fs_storage_id`` stale.

A stale ``NULL`` ``fs_filename`` defeats the gate in
``IrBinary._get_fs_attachment_for_field``, so base Odoo falls through
to ``Stream.from_attachment``. The base implementation passes the
raw ``store_fname`` (e.g. ``"azure_attachments://<hash>"``) straight
to ``os.stat``, which raises ``FileNotFoundError`` — user-visible as
500s on every ``/web/image/<res_model>/<id>/<field>`` call that
touches one of these records.

Observed on production 2026-04-20 → 2026-04-22: when a site flipped
``fs.storage.use_as_default_for_attachments`` to an ``abfs`` (Azure
Blob) backend, the Odoo deploy that followed regenerated thousands of
asset bundles and ran background image updates, every one via
``write({"datas": ...})``. 36,804 ``ir.attachment`` rows entered the
broken state in under 48 h and every ``/web/image`` hit for those
records returned 500 — assets included, so the webclient rendered as
a blank page in every browser.

Widen the guard to also fire on ``"datas"`` / ``"raw"`` so every
write that actually changes the bytes leaves ``fs_filename``
consistent with the new ``store_fname``.
``_enforce_meaningful_storage_filename`` is idempotent and already
no-ops when nothing needs to change, so the broader guard has no
downside for the existing ``"name"`` path.

Signed-off-by: TecnologiaIG <tecnologia@intensegroupgt.com>
@TecnologiaIG TecnologiaIG force-pushed the 16.0-fs_attachment-write-guard-datas branch from 115aec6 to b9a51a9 Compare April 22, 2026 11:36
Copy link
Copy Markdown
Contributor

@lmignon lmignon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the fix @TecnologiaIG . Can you avoid tu manually update the version into the manifest since it will be updated by the bot in charge of the merge. Can I also ask you to add a unittest to cover your modification?

Regards

"name": "Base Attachment Object Store",
"summary": "Store attachments on external object store",
"version": "16.0.2.0.1",
"version": "16.0.2.0.4",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version should not be updated manually.

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