From 0677b0ca1eee360a79b772437fb6dbff911eaed4 Mon Sep 17 00:00:00 2001 From: Azhar Date: Sun, 18 Oct 2026 22:09:27 +0530 Subject: [PATCH] serve restic: fix temporary errors confusing restic into leaving locks behind deleteObject() replied 404 for any error from newObject(), while serveObject() in the same file replies 404 only for fs.ErrorObjectNotFound and 500 otherwise. restic treats a 404 on DELETE as final and never retries, so a transient lookup failure while removing a lock left the lock behind and broke the next exclusive operation (eg forget --prune failing with "repository is already locked"). This change mirrors serveObject() handling of 404/500 errors in deleteObject(). Fixes #10100 Co-authored-by: bunnysayzz --- cmd/serve/restic/restic.go | 6 +++++- cmd/serve/restic/restic_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/cmd/serve/restic/restic.go b/cmd/serve/restic/restic.go index ce650a43f..59a43b35d 100644 --- a/cmd/serve/restic/restic.go +++ b/cmd/serve/restic/restic.go @@ -509,7 +509,11 @@ func (s *server) deleteObject(w http.ResponseWriter, r *http.Request) { o, err := s.newObject(r.Context(), remote) if err != nil { fs.Debugf(remote, "Delete request error: %v", err) - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + if errors.Is(err, fs.ErrorObjectNotFound) { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + } else { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } return } diff --git a/cmd/serve/restic/restic_test.go b/cmd/serve/restic/restic_test.go index 0fa27bc79..9ddb09d12 100644 --- a/cmd/serve/restic/restic_test.go +++ b/cmd/serve/restic/restic_test.go @@ -229,6 +229,28 @@ func TestServeErrors(t *testing.T) { checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusNotFound)}) } +func TestDeleteErrors(t *testing.T) { + ctx := context.Background() + // setup rclone with a local backend in a temporary directory + tempdir := t.TempDir() + opt := newOpt() + + // make a new file system in the temp dir + f := &newObjectErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})} + s, err := newServer(ctx, f, &opt) + require.NoError(t, err) + router := s.server.Router() + + // a lookup failure that is not "not found" must be a 500 so + // restic retries the delete instead of treating it as final + f.err = errors.New("oops") + req := newRequest(t, "DELETE", "/test/locks/0123456789abcdef", nil) + checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusInternalServerError)}) + + f.err = fs.ErrorObjectNotFound + checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusNotFound)}) +} + func TestRc(t *testing.T) { servetest.TestRc(t, rc.Params{ "type": "restic",