Skip to content

RecursionError: maximum recursion depth exceeded when saving a File that shares content_hash with another File #132

Description

@dvdl16

Summary

When two (or more) File records share the same content_hash (duplicate content), saving one of them triggers an infinite save loop between the duplicate rows via validate()associate_files()save(), ending in:

builtins.RecursionError: maximum recursion depth exceeded while calling a Python object

Environment

  • App: cloud_storage (version-15)
  • Frappe: v15

Root cause

validate() unconditionally calls self.associate_files():

def validate(self):
    self.associate_files()
    ...

When a duplicate-content File exists, associate_files() looks up the other File by content_hash, loads it, and saves it:

associated_doc = frappe.get_value(
    "File",
    {"content_hash": self.content_hash, "name": ["!=", self.name], "is_folder": False},
)
if associated_doc and associated_doc != self.name:
    existing_file = frappe.get_doc("File", associated_doc)
    ...
    existing_file.save()   # <-- re-enters validate() -> associate_files()

That existing_file.save() re-runs validate() on the other row, which calls associate_files() again - and finds the original file by content_hash and saves it. The two rows save each other in an endless A → B → A → … loop until Python hits the recursion limit.

Steps to reproduce

  1. Have two File records with identical content (same content_hash). 2. Trigger a save/validate on one of them (e.g. attach it to a document, or any operation that re-validates the File). 3. validate()associate_files() saves the duplicate, which validates and saves back → RecursionError.

Expected behaviour

Associating duplicate-content files should not cause validate() to recurse into itself indefinitely. A File being saved as part of the association process should not re-trigger another round of association.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions