From a2674bea6a56e5906c14edde669a18d8fe8afd71 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Tue, 13 Oct 2026 18:00:39 -0700 Subject: [PATCH] thumbs: a GPU-access run already in flight refuses a second (409) The sweep's battery hit the route three times; the second and third runs only collided with the first's restart timer. Co-Authored-By: Claude Fable 5.1 --- PLAN.md | 6 ++++++ internal/api/thumbs.go | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/PLAN.md b/PLAN.md index 68c4a1a..cdfe3f7 100644 --- a/PLAN.md +++ b/PLAN.md @@ -810,6 +810,12 @@ something long in one, switch away and back, refresh — it's all still there. not exist), the task list read a task's Running flag outside its lock (a real data race, never seen), the Virtualization export config was written in place rather than atomically, and the dead code. Nothing in auth, the proxies or the shell boundaries. + One self-inflicted finding: the battery's skip list left out `POST + /api/files/thumbs/gpu-access`, so the sweep pressed *Allow GPU access* on `core` three + times over — the drop-in landed and the hub restarted (that is what the headless pass + saw as a refused connection). Kept, since it is the fix the tab offers; `core` makes + previews and re-encodes on its UHD iGPU now. The handler refuses a second run while one + is in flight (409), since the second `systemd-run` only collided with the first's timer. - **Docker read-only view** — `docker ps`/logs; dockhand stays the source of truth. - **Notifications** — push on service failure / SMART warning / UPS event. - **Terminal extras** — search, copy-mode, font/theme, split panes, upload via diff --git a/internal/api/thumbs.go b/internal/api/thumbs.go index fa773d3..3c9ebde 100644 --- a/internal/api/thumbs.go +++ b/internal/api/thumbs.go @@ -403,6 +403,10 @@ func (s *Server) thumbsGPUAccess(w http.ResponseWriter, r *http.Request) { jsonErr(w, 400, "nothing to do: Ham Lab is root, has no root, or is not under systemd here") return } + if t, busy := s.tasks.RunningOf("thumbs:gpu"); busy { // a second run would only collide with the first's restart timer + jsonErr(w, 409, "already being done (task "+t.ID+")") + return + } full := priv.Wrap("sh", "-c", gpuAccessScript) t, err := s.tasks.Start("thumbs:gpu", "give Ham Lab access to the GPU", exec.Command(full[0], full[1:]...)) if err != nil {