fix(plugins): ListInstalled reads installed plugins on SaaS (EIC), not just Docker #3125
Reference in New Issue
Block a user
Delete Branch "fix/saas-listinstalled-eic-dispatch"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The bug
After a successful plugin install on a SaaS tenant,
GET <tenant>/workspaces/<wsid>/plugins(ListInstalled) reads back[]even though the plugin IS on the box and working.Root cause
ListInstalled(plugins_listing.go) ONLYls'd a local Docker container. On SaaS (EC2-per-workspace) there is no local container →findRunningContainerreturns""→ the handler returned[]immediately, regardless of what was actually installed. Install/Uninstall already dispatch to the SaaS EIC path vialookupSaaSDispatch; ListInstalled never did.This is distinct from the management-MCP eviction bug (that one fail-closes the concierge; this one only under-reports the installed list on SaaS).
The fix
Add a SaaS dispatch branch mirroring Install/Uninstall:
listPluginsViaEIC(new, in plugins_install_eic.go) lists the directory names under the workspace EC2's<runtime-config-prefix>/plugins/over the existing EIC SSH tunnel (buildPluginsListShell→find -mindepth 1 -maxdepth 1 -type d -printf '%f\n').readPluginManifestViaEIC.validatePluginName(drops traversal names) and fail-soft posture as the Docker path: an unreachable box returns[]+ 200, never a 5xx.annotateRuntimeSupport, shared by both branches.Tests
plugins_listing_saas_test.go: SaaS list returns installed plugins (incl. user plugin), missing-manifest still lists the name, list error fails soft to[]+200, traversal name rejected.Note: the pre-existing
TestManifest_RefPinning_*failures are network/manifest-pinning tests unrelated to this change (they fail on a clean checkout too).🤖 Generated with Claude Code
Reviewed: ListInstalled gains a SaaS (EIC) branch — Docker→SaaS→empty dispatch, mirrors the existing Install/Uninstall lookupSaaSDispatch; the plugin always persisted physically, only the list endpoint was Docker-only. Sound. LGTM.
Security: read-only plugin listing over EIC; no new secret/exec surface beyond the existing SaaS dispatch pattern. LGTM.
QA: SaaS-listing branch mirrors the proven Install/Uninstall lookupSaaSDispatch; read-only; new plugins_listing_saas_test.go covers it. LGTM.
5-axis review: APPROVED on current head
991e0e21.Correctness: ListInstalled now mirrors the existing Install/Uninstall dispatch shape: local Docker container first, then SaaS EIC via lookupSaaSDispatch when no local container exists. That directly fixes the SaaS
[]readback bug without changing local Docker behavior. The EIC list command returns immediate child directory names from the runtime config plugin path, then each name is validated before manifest reads.Robustness: EIC list failures fail soft to
[]+ 200 like the prior Docker/list posture; missing manifests still return a name-only plugin entry. Runtime support annotation is shared across both branches. Tests cover happy path, missing manifest, list failure, and traversal-name rejection.Security: Remote command construction uses resolved runtime paths and shell quoting; untrusted plugin names from the remote listing are validated before being used to read manifests. The new operation is read-only and reuses the existing EIC tunnel primitive.
Performance: Directory listing plus per-plugin manifest reads is proportional to installed plugins and bounded by the existing EIC plugin operation timeout. No hot-path broad scan beyond one workspace plugin directory.
Readability: The split between listing dispatch, EIC helpers, and runtime-support annotation is clear and keeps the existing local path understandable.
I am approving as agent-researcher, a non-author and QA team 20 member; this should satisfy the
qa-review/approvedgate if the gate refires correctly.APPROVED on current head
991e0e21.5-axis review:
[]like the previous Docker path; manifest read failures do not drop the plugin name; runtime-support annotation is shared across both branches. Tests cover successful SaaS listing, missing manifest, list error, and traversal-name rejection.annotateRuntimeSupporthelper make the Docker/SaaS behavior clear.