Deleting a project leaves its documentation on disk forever #164

Closed
opened 2026-09-09 14:08:40 +02:00 by qwc · 0 comments
Owner

Noticed while investigating #163.

handleAdminDeleteProject (internal/handler/admin_projects.go:389) removes the project's versions from the search index, then calls h.projects.Delete, which is a bare DELETE FROM projects WHERE id = ?. Nothing in that path touches storage, and FilesystemStorage has no DeleteProject method at all — only DeleteVersion.

So every project ever deleted has left its entire directory under storage.base_path behind, with all versions in it. Nothing ever reads those directories again and nothing reports them, so the disk usage just grows.

Why it matters beyond the wasted space

  • A project deleted and later recreated with the same slug inherits the old directory, so stale files from the previous project can be served under the new one wherever a version tag happens to match.
  • Retention only ever deletes versions of projects that still exist, so it can never reclaim any of this.
  • On the production instance in #163 the leftover directories were the only remaining record of what those deleted projects contained — the versions rows naming their storage_path had to be dumped before the orphan cleanup could remove them.

Fix

Give the storage interface a DeleteProject(slug) and call it from the delete handler after the database row is gone, logging rather than failing the request if the directory cannot be removed — the project is deleted either way, and a half-failed delete should not leave the row behind.

Existing leftover directories are not cleaned up by this; they have no rows pointing at them any more. Worth a separate look at whether startup's ReconcileStorage should report directories with no project.

Assisted-by: Claude Opus 5

Noticed while investigating #163. `handleAdminDeleteProject` (`internal/handler/admin_projects.go:389`) removes the project's versions from the search index, then calls `h.projects.Delete`, which is a bare `DELETE FROM projects WHERE id = ?`. Nothing in that path touches storage, and `FilesystemStorage` has no `DeleteProject` method at all — only `DeleteVersion`. So every project ever deleted has left its entire directory under `storage.base_path` behind, with all versions in it. Nothing ever reads those directories again and nothing reports them, so the disk usage just grows. ## Why it matters beyond the wasted space - A project deleted and later recreated with the same slug inherits the old directory, so stale files from the previous project can be served under the new one wherever a version tag happens to match. - Retention only ever deletes versions of projects that still exist, so it can never reclaim any of this. - On the production instance in #163 the leftover directories were the only remaining record of what those deleted projects contained — the `versions` rows naming their `storage_path` had to be dumped before the orphan cleanup could remove them. ## Fix Give the storage interface a `DeleteProject(slug)` and call it from the delete handler after the database row is gone, logging rather than failing the request if the directory cannot be removed — the project is deleted either way, and a half-failed delete should not leave the row behind. Existing leftover directories are not cleaned up by this; they have no rows pointing at them any more. Worth a separate look at whether startup's `ReconcileStorage` should report directories with no project. Assisted-by: Claude Opus 5
qwc closed this issue 2026-09-09 14:25:51 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
qwc-open/asiakirjat#164
No description provided.