From cc99d3fff4d375491e7b67816679ee8bbde58293 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Mon, 1 Jun 2026 00:51:59 +0000 Subject: [PATCH 1/2] fix(plugins): log silently ignored execAsRoot errors during uninstall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugin uninstall had two sites where execAsRoot errors were discarded: - Skill directory removal (plugins_install.go:125) — orphaned skill dirs if rm -rf failed silently - CLAUDE.md marker stripping (plugins_install_pipeline.go:326) — stale plugin content left in CLAUDE.md if awk script failed Both now log the error without failing the overall uninstall (best-effort cleanup), giving operators visibility into incomplete uninstalls. Co-Authored-By: Claude Opus 4.7 --- workspace-server/internal/handlers/plugins_install.go | 6 ++++-- .../internal/handlers/plugins_install_pipeline.go | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/workspace-server/internal/handlers/plugins_install.go b/workspace-server/internal/handlers/plugins_install.go index 9696af8bc..5894b569d 100644 --- a/workspace-server/internal/handlers/plugins_install.go +++ b/workspace-server/internal/handlers/plugins_install.go @@ -171,9 +171,11 @@ func (h *PluginsHandler) uninstallViaDocker(ctx context.Context, c *gin.Context, log.Printf("Plugin uninstall: skipping invalid skill name %q in %s: %v", skill, pluginName, err) continue } - _, _ = h.execAsRoot(ctx, containerName, []string{ + if _, rmErr := h.execAsRoot(ctx, containerName, []string{ "rm", "-rf", "/configs/skills/" + skill, - }) + }); rmErr != nil { + log.Printf("Plugin uninstall: failed to remove skill %s from %s: %v", skill, workspaceID, rmErr) + } } // 3. Delete the plugin directory itself (as root to handle file ownership). diff --git a/workspace-server/internal/handlers/plugins_install_pipeline.go b/workspace-server/internal/handlers/plugins_install_pipeline.go index ce79f45ac..7c158c00e 100644 --- a/workspace-server/internal/handlers/plugins_install_pipeline.go +++ b/workspace-server/internal/handlers/plugins_install_pipeline.go @@ -417,7 +417,9 @@ func (h *PluginsHandler) stripPluginMarkersFromMemory(ctx context.Context, conta `awk 'BEGIN{skip=0; blanks=0} /^%s/{skip=1; blanks=0; next} skip==1 && /^[[:space:]]*$/{blanks++; if(blanks>=2){skip=0; print; next} next} /^# Plugin: /{if(skip==1)skip=0} skip==1{next} {print}' /configs/CLAUDE.md > /tmp/claude.new && mv /tmp/claude.new /configs/CLAUDE.md`, regexpEscapeForAwk(marker), ) - _, _ = h.execAsRoot(ctx, containerName, []string{"bash", "-c", script}) + if _, awkErr := h.execAsRoot(ctx, containerName, []string{"bash", "-c", script}); awkErr != nil { + log.Printf("Plugin uninstall: failed to strip markers from CLAUDE.md for %s in %s: %v", pluginName, workspaceID, awkErr) + } } // regexpEscapeForAwk escapes characters that have special meaning inside an -- 2.52.0 From 48b6011e1713099549b4d9ae9f2700d6a96adb3e Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Fri, 5 Jun 2026 04:09:20 +0000 Subject: [PATCH 2/2] fix(2047): pass workspaceID to stripPluginMarkersFromMemory --- workspace-server/internal/handlers/plugins_install.go | 2 +- workspace-server/internal/handlers/plugins_install_pipeline.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspace-server/internal/handlers/plugins_install.go b/workspace-server/internal/handlers/plugins_install.go index 5894b569d..117049fd7 100644 --- a/workspace-server/internal/handlers/plugins_install.go +++ b/workspace-server/internal/handlers/plugins_install.go @@ -161,7 +161,7 @@ func (h *PluginsHandler) uninstallViaDocker(ctx context.Context, c *gin.Context, // 1. Strip plugin's rule/fragment markers from CLAUDE.md (mirrors // AgentskillsAdaptor.uninstall lines 184-188). Best-effort: if // the user edited CLAUDE.md, our marker stays untouched. - h.stripPluginMarkersFromMemory(ctx, containerName, pluginName) + h.stripPluginMarkersFromMemory(ctx, workspaceID, containerName, pluginName) // 2. Remove copied skill dirs declared in the plugin's plugin.yaml. for _, skill := range skillNames { diff --git a/workspace-server/internal/handlers/plugins_install_pipeline.go b/workspace-server/internal/handlers/plugins_install_pipeline.go index 7c158c00e..17124f20e 100644 --- a/workspace-server/internal/handlers/plugins_install_pipeline.go +++ b/workspace-server/internal/handlers/plugins_install_pipeline.go @@ -393,7 +393,7 @@ func (h *PluginsHandler) readPluginSkillsFromContainer(ctx context.Context, cont // `# Plugin: /` — mirrors AgentskillsAdaptor.uninstall's stripping // logic so install/uninstall are symmetric. Best-effort: silent on read or // write failure, since the rest of uninstall must still succeed. -func (h *PluginsHandler) stripPluginMarkersFromMemory(ctx context.Context, containerName, pluginName string) { +func (h *PluginsHandler) stripPluginMarkersFromMemory(ctx context.Context, workspaceID, containerName, pluginName string) { // Use sed via bash -c for atomic in-place delete: drop the marker line // and the blank line that follows it (install adds a leading blank line // before the marker via append_to_memory). Three sed passes mirror the -- 2.52.0