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
- 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.
Summary
When two (or more)
Filerecords share the samecontent_hash(duplicate content), saving one of them triggers an infinite save loop between the duplicate rows viavalidate()→associate_files()→save(), ending in:builtins.RecursionError: maximum recursion depth exceeded while calling a Python objectEnvironment
cloud_storage(version-15)Root cause
validate()unconditionally callsself.associate_files():When a duplicate-content File exists,
associate_files()looks up the other File bycontent_hash, loads it, and saves it:That
existing_file.save()re-runsvalidate()on the other row, which callsassociate_files()again - and finds the original file bycontent_hashand saves it. The two rows save each other in an endlessA → B → A → …loop until Python hits the recursion limit.Steps to reproduce
Filerecords with identical content (samecontent_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.