Uploading to a held path fails silently — surface the legal-hold refusal in the UI #172

Closed
opened 2026-08-10 00:48:47 +00:00 by Cordy · 1 comment
Owner

Dogfood note (Nikola): uploading a file to a path under a legal hold does nothing — no upload, but also no message. The server side is correct (the hold decorator refuses the write with ErrHeld → HTTP 403 with the "legal hold: this content is protected until the hold is lifted" message); the upload path in the UI swallows the error.

Fix: upload failures must always surface the server's error message — a visible dialog/toast, with the hold message verbatim, for both the plain upload path and the TUS path (TUS finalize is where a held-path refusal lands for large files). While in there, verify the same for delete/rename/overwrite on held paths — every hold refusal a user can trigger should say why, not fizzle.

This also covers the general class: any mutating API error that today ends in a silent catch should reach the status line at minimum.

Dogfood note (Nikola): uploading a file to a path under a legal hold does nothing — no upload, but also no message. The server side is correct (the hold decorator refuses the write with `ErrHeld` → HTTP 403 with the "legal hold: this content is protected until the hold is lifted" message); the upload path in the UI swallows the error. Fix: upload failures must always surface the server's error message — a visible dialog/toast, with the hold message verbatim, for both the plain upload path and the TUS path (TUS finalize is where a held-path refusal lands for large files). While in there, verify the same for delete/rename/overwrite on held paths — every hold refusal a user can trigger should say why, not fizzle. This also covers the general class: any mutating API error that today ends in a silent `catch` should reach the status line at minimum.
Author
Owner

Resolved for the reported case in current code (v0.6.8) — verified by reading web/static/index.html + internal/api/api.go.

The backend maps storage.ErrHeld403 with the hold text (writeErr), api() extracts {error} from it, and uploadFiles routes the failure through sayError — a prominent red banner (added explicitly for #172) that carries the server's own message. So a POST upload to a held path now shows:

Upload failed: legal hold: this content is protected until the hold is lifted

— not silent.

One known minor edge, tracked here: large-file resumable (TUS) uploads. tusUpload only special-cases 404/410/507/413; a 403 hold lands in the generic retry branch and, after retries, throws chunk failed (403) instead of the hold message. Low priority (normal uploads surface the hold correctly). Happy to add a TUS branch that reads the 403 body and throws it verbatim if this bites in practice.

Closing the umbrella as resolved for the reported (silent-failure) behavior — reopen if the banner doesn't appear.

Resolved for the reported case in current code (v0.6.8) — verified by reading `web/static/index.html` + `internal/api/api.go`. The backend maps `storage.ErrHeld` → **403** with the hold text (`writeErr`), `api()` extracts `{error}` from it, and `uploadFiles` routes the failure through **`sayError`** — a prominent red banner (added explicitly for #172) that carries the server's own message. So a POST upload to a held path now shows: > Upload failed: legal hold: this content is protected until the hold is lifted — not silent. **One known minor edge, tracked here:** large-file **resumable (TUS)** uploads. `tusUpload` only special-cases 404/410/507/413; a 403 hold lands in the generic retry branch and, after retries, throws `chunk failed (403)` instead of the hold message. Low priority (normal uploads surface the hold correctly). Happy to add a TUS branch that reads the 403 body and throws it verbatim if this bites in practice. Closing the umbrella as resolved for the reported (silent-failure) behavior — reopen if the banner doesn't appear.
Cordy closed this issue 2026-08-11 16:53:14 +00:00
Sign in to join this conversation.
No labels
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: Cordy/Cairn#172
No description provided.