#4502·hindsight

Deleting a bank renamed with rename-bank leaves its stored files in the object store

Author: ebarkhordarCreated Sep 18, 2026Updated Sep 18, 2026

rename-bank moves every column called bank_id. An object key is not one of those: attachments.storage_key and documents.file_storage_key spell the bank id inside the key string. After a rename the rows carry the new id and their keys still carry the old one.

The sweep at the end of delete_bank then misses them. It deletes bank_storage_prefix(bank_id), which is the new prefix, plus any row whose key is NOT LIKE 'tenants/%', which is there for the pre-tenant layout. A renamed bank's keys start with tenants/ and sit under the old id, so they match neither and the bytes stay after the bank is gone. On S3 or GCS that is content a delete was meant to remove, still billed.

Reproduced at bcca388 against postgres 18 + pgvector, one PNG attachment retained into old_id:

key = attachment_storage_key(old_id, "doc", png)   # tenants/public/banks/<old_id>/documents/doc/attachments/sha256-...
await _run_rename_bank(db_url, "public", old_id, new_id, dry_run=False)
await mem.delete_bank(new_id, request_context=rc)
await mem._file_storage.retrieve(key)              # returns the bytes

The rename reports {'attachments': 1, 'banks': 1, 'chunks': 1, 'documents': 1, 'entities': 12, 'memory_links': 190, 'memory_units': 10}, and attachments.storage_key comes back unchanged. The same script with the rename step removed raises FileNotFoundError on that last line, so the sweep itself works.

One thing I expected and did not get: the file stays readable. GET /v1/default/files/download/<key> still returns 200 after the rename, because the route parses the bank id out of the key and the old id resolves. So this is a leak rather than lost data.

I have a PR that widens the legacy_files predicate from NOT LIKE 'tenants/%' to "not under this bank's current prefix", which covers the pre-tenant keys and the renamed ones in one condition. If you want rename-bank to rewrite the keys instead, say so and I will do that.