From 18824f841d6bd4cb6e0610dd2245f17c02a09e92 Mon Sep 17 00:00:00 2001 From: Thieu Nguyen <79964592+thieung@users.noreply.github.com> Date: Wed, 25 Mar 2026 08:30:49 +0700 Subject: [PATCH] fix(pkg-helper): restore group ownership on apk-packages persist file (#456) * fix(pkg-helper): restore group ownership on apk-packages persist file persistRemove() uses write-to-temp-then-rename which creates the new file as root:root. The goclaw process (uid 1000, gid 1000) can't read it, causing ListInstalledPackages to return nil for system packages. After any package uninstall, the UI shows an empty package list. Fix: chown listFile to root:goclaw (0:1000) after rename in persistRemove and after initial creation in persistAdd. * fix(ui): reset uninstall success icon to trash after timeout After uninstalling a package, the success checkmark icon stayed permanently instead of reverting to the delete (trash) icon. Add setTimeout to reset actionStatus from "success" to "idle" after 2s, matching the existing pattern used for error states. --- cmd/pkg-helper/main.go | 13 +++++++++++++ ui/web/src/pages/packages/packages-page.tsx | 1 + 2 files changed, 14 insertions(+) diff --git a/cmd/pkg-helper/main.go b/cmd/pkg-helper/main.go index cfc35b42..d367fb9b 100644 --- a/cmd/pkg-helper/main.go +++ b/cmd/pkg-helper/main.go @@ -183,6 +183,10 @@ func persistAdd(pkg string) { } } + created := false + if _, err := os.Stat(listFile); os.IsNotExist(err) { + created = true + } f, err := os.OpenFile(listFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) if err != nil { slog.Warn("pkg-helper: persist add failed", "error", err) @@ -190,6 +194,10 @@ func persistAdd(pkg string) { } defer f.Close() fmt.Fprintln(f, pkg) + // Ensure group ownership allows the goclaw process to read the file. + if created { + os.Chown(listFile, 0, 1000) //nolint:errcheck + } } // persistRemove removes a package name from the apk persist file. @@ -217,7 +225,12 @@ func persistRemove(pkg string) { if err := os.Rename(tmpFile, listFile); err != nil { slog.Warn("pkg-helper: persist remove rename failed", "error", err) os.Remove(tmpFile) //nolint:errcheck + return } + // Restore group ownership so the goclaw process (uid 1000, gid 1000) can read the file. + // Without this, the renamed file inherits root:root from the temp file, + // causing ListInstalledPackages to return nil for system packages. + os.Chown(listFile, 0, 1000) //nolint:errcheck } func apkListFile() string { diff --git a/ui/web/src/pages/packages/packages-page.tsx b/ui/web/src/pages/packages/packages-page.tsx index 6e71b172..b33f4524 100644 --- a/ui/web/src/pages/packages/packages-page.tsx +++ b/ui/web/src/pages/packages/packages-page.tsx @@ -131,6 +131,7 @@ function PackageSection({ title, placeholder, packages, loading, onInstall, onUn const res = await onUninstall(name); if (res.ok) { setActionStatuses((s) => ({ ...s, [name]: "success" })); + setTimeout(() => setActionStatuses((s) => ({ ...s, [name]: "idle" })), 2000); } else { setActionStatuses((s) => ({ ...s, [name]: "error" })); setTimeout(() => setActionStatuses((s) => ({ ...s, [name]: "idle" })), 3000);