EditPost: file rename leaves DB and disk inconsistent on partial failure #17

Open
opened 2026-04-13 15:58:22 +02:00 by arne · 0 comments
Owner

`internal/web/handler.go` `EditPost` does the metadata UPDATE before the file rename. If the rename fails (typically because `book.FilePath` in the DB has drifted from disk), the user sees an error but the metadata write has already committed. Subsequent edits hit the same stale path and re-error.

Concrete repro from prod: book #110 had `file_path = "Scott, James C.;/Seeing Like a State.epub"` in DB while the actual file lived at `James C. Scott/Seeing Like a State.epub` (presumably from an earlier rename that updated DB but failed mid-way, or a manual move). Editing the author to "James C. Scott" then triggered:

```
file rename failed: open /opt/books/library/Scott, James C.;/Seeing Like a State.epub: no such file or directory
```

Two things worth doing:

  1. Defensive rename. When `lib.Rename` finds the source missing AND the computed destination already exists, treat it as a successful no-op and let the handler update `file_path`/`cover_path` to match. (Risky if the destination collision is unrelated — collision-free naming uses " (2)" suffixing, so this case strongly suggests prior rename to the same path.)

  2. Atomic metadata + rename. Don't commit the metadata UPDATE until the file rename succeeds, OR rollback the metadata change when rename fails. Today the partial-failure window leaves DB ahead of disk and every subsequent edit re-errors.

Workaround for stuck rows: `UPDATE books SET file_path='', cover_path='' WHERE id=` directly against SQLite.

\`internal/web/handler.go\` \`EditPost\` does the metadata UPDATE before the file rename. If the rename fails (typically because \`book.FilePath\` in the DB has drifted from disk), the user sees an error but the metadata write has already committed. Subsequent edits hit the same stale path and re-error. Concrete repro from prod: book #110 had \`file_path = "Scott, James C.;/Seeing Like a State.epub"\` in DB while the actual file lived at \`James C. Scott/Seeing Like a State.epub\` (presumably from an earlier rename that updated DB but failed mid-way, or a manual move). Editing the author to "James C. Scott" then triggered: \`\`\` file rename failed: open /opt/books/library/Scott, James C.;/Seeing Like a State.epub: no such file or directory \`\`\` Two things worth doing: 1. **Defensive rename.** When \`lib.Rename\` finds the source missing AND the computed destination already exists, treat it as a successful no-op and let the handler update \`file_path\`/\`cover_path\` to match. (Risky if the destination collision is unrelated — collision-free naming uses " (2)" suffixing, so this case strongly suggests prior rename to the same path.) 2. **Atomic metadata + rename.** Don't commit the metadata UPDATE until the file rename succeeds, OR rollback the metadata change when rename fails. Today the partial-failure window leaves DB ahead of disk and every subsequent edit re-errors. Workaround for stuck rows: \`UPDATE books SET file_path='<correct>', cover_path='<correct>' WHERE id=<n>\` directly against SQLite.
Sign in to join this conversation.
No labels
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
arne/books#17
No description provided.