From 8de7b786eafea85d6e4edabc7d158ec765930684 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:47:35 -0400 Subject: [PATCH 1/8] mcp install: use the HTTP host the gateway reports cfg.http_url guessed the REST host from the gRPC host, which is wrong on hosted deployments (gateway.stage.beam.cloud vs app.stage.beam.cloud); ServiceClient.http already resolves it from ExportWorkspaceConfig. Co-authored-by: Cursor --- sdk/src/beta9/cli/mcp.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sdk/src/beta9/cli/mcp.py b/sdk/src/beta9/cli/mcp.py index 5c2f885c4..8dc8b046f 100644 --- a/sdk/src/beta9/cli/mcp.py +++ b/sdk/src/beta9/cli/mcp.py @@ -12,6 +12,7 @@ import click from .. import terminal +from ..channel import ServiceClient from ..config import get_config_context, get_settings from . import extraclick from .extraclick import ClickCommonGroup @@ -34,13 +35,19 @@ def mcp(): def _server(context: Optional[str]) -> Dict[str, str]: + """The MCP URL on the gateway's HTTP host, as the gateway reports it, plus the context token.""" cfg = get_config_context(context or extraclick.selected_context()) if not cfg.token: terminal.error( f"No token for this context; run `{terminal.cli_name()} configure` first.", code="NOT_AUTHENTICATED", ) - return {"url": f"{cfg.http_url}/api/v1/mcp", "authorization": f"Bearer {cfg.token}"} + client = ServiceClient(cfg) + try: + base_url = client.http.base_url + finally: + client.close() + return {"url": f"{base_url}/api/v1/mcp", "authorization": f"Bearer {cfg.token}"} def _entry(client: str, server: Dict[str, str]) -> Dict[str, Any]: From ab29be9b1fde93da80d78c94d490d0553a391655 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:51:47 -0400 Subject: [PATCH 2/8] MCP delete_app stops deployments and containers It only soft-deleted the app record, leaving every version active. It now runs the REST DeleteApp handler in-process, which stops deployments and containers first. Co-authored-by: Cursor --- pkg/api/v1/mcp_tools.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/api/v1/mcp_tools.go b/pkg/api/v1/mcp_tools.go index 810b547d1..d55b55e0a 100644 --- a/pkg/api/v1/mcp_tools.go +++ b/pkg/api/v1/mcp_tools.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "regexp" "sort" "strings" @@ -212,9 +213,14 @@ func (g *MCPGroup) deleteApp(ctx context.Context, a *auth.AuthInfo, args toolArg if err != nil { return nil, err } - if err := g.backendRepo.DeleteApp(ctx, app.ExternalId); err != nil { + // The REST handler stops deployments and containers before removing the record. + res, err := g.serve(ctx, http.MethodDelete, g.config.GatewayService.HTTP.GetExternalURL()+HttpServerBaseRoute+"/app/"+a.Workspace.ExternalId+"/"+app.ExternalId, nil, nil) + if err != nil { return nil, err } + if status := res.(map[string]any)["status"].(int); status >= 300 { + return nil, fmt.Errorf("delete app: HTTP %d", status) + } return map[string]any{"deleted": app.Name, "app_id": app.ExternalId}, nil } From 454c89f223ce3f98f4a5b868cb7f46fca252f395 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:20:19 -0400 Subject: [PATCH 3/8] Deleting a database removes its app record The deployments and secrets went but the app stayed, so the dashboard kept a stopped ghost app for every deleted database. Co-authored-by: Cursor --- pkg/gateway/services/database.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/gateway/services/database.go b/pkg/gateway/services/database.go index 90f6ce2df..5ec9fb9d8 100644 --- a/pkg/gateway/services/database.go +++ b/pkg/gateway/services/database.go @@ -376,7 +376,7 @@ func (gws *GatewayService) recycleDependents(ctx context.Context, workspace *typ return nil } -// DeleteDatabaseService removes the deployment and its secrets; the disk is kept. +// DeleteDatabaseService removes the deployments, secrets and app record; the disk is kept. func (gws *GatewayService) DeleteDatabaseService(ctx context.Context, authInfo *auth.AuthInfo, name string) error { deployments, product, err := gws.databaseDeployments(ctx, authInfo.Workspace, name) if err != nil { @@ -396,7 +396,7 @@ func (gws *GatewayService) DeleteDatabaseService(ctx context.Context, authInfo * return fmt.Errorf("delete secret %s: %w", secret, err) } } - return nil + return gws.backendRepo.DeleteApp(ctx, deployments[0].App.ExternalId) } // ListDatabaseServices returns the newest version of each database service. From 5435272dac1d7f7beec09af3ef419ec97de64cc5 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:51:51 -0400 Subject: [PATCH 4/8] Containers can bind ports below 1024; hard links to symlinks survive indexing - CAP_NET_BIND_SERVICE and CAP_SETPCAP join the base capability set, matching Docker's defaults. nginx and every other image that listens on 80/443 failed with EACCES. - clip: a hard link replays as a copy of its target node, so a hard link to a symlink stays a symlink. nix's optimised store hard-links symlinks into /nix/store/.links, which left nixpacks-built images with unreadable libz.so.1 and friends. Co-authored-by: Cursor --- go.mod | 4 ++-- go.sum | 4 ++-- pkg/runtime/base_runc_config.json | 12 +++++++++--- pkg/runtime/base_runsc_config.json | 12 +++++++++--- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 330fabc6c..9337c884c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2 v1.316.1 github.com/aws/aws-sdk-go-v2/service/ecr v1.27.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 - github.com/beam-cloud/clip v0.0.0-20260907023706-9420edd6f9ec + github.com/beam-cloud/clip v0.0.0-20260923204331-e0b058b47d87 github.com/beam-cloud/go-runc v0.0.0-20250911154456-bb45084abfe1 github.com/beam-cloud/goproc v0.1.12 github.com/beam-cloud/redislock v0.0.0-20250201162619-1b534b3be324 @@ -59,6 +59,7 @@ require ( github.com/redis/go-redis/v9 v9.5.1 github.com/rs/zerolog v1.34.0 github.com/s2-streamstore/s2-sdk-go v0.18.0 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sashabaranov/go-openai v1.35.7 github.com/shirou/gopsutil/v4 v4.26.5 github.com/sirupsen/logrus v1.9.4 @@ -152,7 +153,6 @@ require ( github.com/pires/go-proxyproto v0.8.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/go.sum b/go.sum index 7992780f8..65bc88c34 100644 --- a/go.sum +++ b/go.sum @@ -161,8 +161,8 @@ github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqx github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ= github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/beam-cloud/clip v0.0.0-20260907023706-9420edd6f9ec h1:zcr7+ud507kcRJBHotXr4hcUzEoqfPEgjtcMW6eCZwo= -github.com/beam-cloud/clip v0.0.0-20260907023706-9420edd6f9ec/go.mod h1:uymwDNMk1qoxElmDSlPzwYqZDriHDnW5VVyfK3YNuz8= +github.com/beam-cloud/clip v0.0.0-20260923204331-e0b058b47d87 h1:+FJZsbRf/xVhHM1DknnTIOghuaZt7Brsn88GX9geYfM= +github.com/beam-cloud/clip v0.0.0-20260923204331-e0b058b47d87/go.mod h1:uymwDNMk1qoxElmDSlPzwYqZDriHDnW5VVyfK3YNuz8= github.com/beam-cloud/geesefs v0.0.0-20260908033220-4484605feb60 h1:fX/Z5XOjVKYsWJO/FIOyCdWgg038BNHEpvp6lsiH49g= github.com/beam-cloud/geesefs v0.0.0-20260908033220-4484605feb60/go.mod h1:q0qGsu0wiOzf/5m9JGLqZ7WteYDaaLGgBwNmstb3og0= github.com/beam-cloud/geesefs/s3ext v0.0.0-20250606164905-2f3593d03f4f h1:XzHOu+erxeBO6D3fKVbd5DAlipl+PYZ3u+Ywb8m7Ovk= diff --git a/pkg/runtime/base_runc_config.json b/pkg/runtime/base_runc_config.json index 8a9462b12..cedbe463c 100644 --- a/pkg/runtime/base_runc_config.json +++ b/pkg/runtime/base_runc_config.json @@ -30,7 +30,9 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD" + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETPCAP" ], "effective": [ "CAP_AUDIT_WRITE", @@ -44,7 +46,9 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD" + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETPCAP" ], "permitted": [ "CAP_AUDIT_WRITE", @@ -58,7 +62,9 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD" + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETPCAP" ], "ambient": [] }, diff --git a/pkg/runtime/base_runsc_config.json b/pkg/runtime/base_runsc_config.json index 329960e8e..052c7783b 100644 --- a/pkg/runtime/base_runsc_config.json +++ b/pkg/runtime/base_runsc_config.json @@ -30,7 +30,9 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD" + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETPCAP" ], "effective": [ "CAP_AUDIT_WRITE", @@ -44,7 +46,9 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD" + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETPCAP" ], "permitted": [ "CAP_AUDIT_WRITE", @@ -58,7 +62,9 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD" + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETPCAP" ], "ambient": [] }, From 7c027c636fbe20cf726c0932982b0ad05bd71f98 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:00:45 -0400 Subject: [PATCH 5/8] Bump clip to master (hard-link fix merged) Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9337c884c..93be96087 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2 v1.316.1 github.com/aws/aws-sdk-go-v2/service/ecr v1.27.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 - github.com/beam-cloud/clip v0.0.0-20260923204331-e0b058b47d87 + github.com/beam-cloud/clip v0.0.0-20260923215631-1c772eb678a5 github.com/beam-cloud/go-runc v0.0.0-20250911154456-bb45084abfe1 github.com/beam-cloud/goproc v0.1.12 github.com/beam-cloud/redislock v0.0.0-20250201162619-1b534b3be324 diff --git a/go.sum b/go.sum index 65bc88c34..3326ceddb 100644 --- a/go.sum +++ b/go.sum @@ -161,8 +161,8 @@ github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqx github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ= github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/beam-cloud/clip v0.0.0-20260923204331-e0b058b47d87 h1:+FJZsbRf/xVhHM1DknnTIOghuaZt7Brsn88GX9geYfM= -github.com/beam-cloud/clip v0.0.0-20260923204331-e0b058b47d87/go.mod h1:uymwDNMk1qoxElmDSlPzwYqZDriHDnW5VVyfK3YNuz8= +github.com/beam-cloud/clip v0.0.0-20260923215631-1c772eb678a5 h1:OCIWtQpT3F+1PHcC5KOaOb+QEw2SkUo9Sh1lygsPPpM= +github.com/beam-cloud/clip v0.0.0-20260923215631-1c772eb678a5/go.mod h1:uymwDNMk1qoxElmDSlPzwYqZDriHDnW5VVyfK3YNuz8= github.com/beam-cloud/geesefs v0.0.0-20260908033220-4484605feb60 h1:fX/Z5XOjVKYsWJO/FIOyCdWgg038BNHEpvp6lsiH49g= github.com/beam-cloud/geesefs v0.0.0-20260908033220-4484605feb60/go.mod h1:q0qGsu0wiOzf/5m9JGLqZ7WteYDaaLGgBwNmstb3og0= github.com/beam-cloud/geesefs/s3ext v0.0.0-20250606164905-2f3593d03f4f h1:XzHOu+erxeBO6D3fKVbd5DAlipl+PYZ3u+Ywb8m7Ovk= From 584ea199a148dda1c705f7dc4baa2a141517597c Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:24:44 -0400 Subject: [PATCH 6/8] runsc restore test: report a pid only after the fake restore ran Restore returns on the first state with a pid; the fake reported one immediately, so on a slow runner the assertion read the bundle before the fake restore had copied it. Co-authored-by: Cursor --- pkg/runtime/runsc_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/runtime/runsc_test.go b/pkg/runtime/runsc_test.go index 53e60901a..251a24d46 100644 --- a/pkg/runtime/runsc_test.go +++ b/pkg/runtime/runsc_test.go @@ -409,7 +409,12 @@ case "$cmd" in cp "$RUNSC_FAKE_BUNDLE/config.json" "$RUNSC_FAKE_MOUNTS" ;; state) - printf '{"id":"container-1","pid":4321,"status":"running"}' + # No pid until restore has run: Restore returns on the first pid it sees. + if [ -f "$RUNSC_FAKE_MOUNTS" ]; then + printf '{"id":"container-1","pid":4321,"status":"running"}' + else + printf '{"id":"container-1","pid":0,"status":"created"}' + fi ;; wait|delete) ;; *) From b301fab967a34916836274b5ecb3a0d6c39d6ec6 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:16:24 -0400 Subject: [PATCH 7/8] Drop CAP_SETPCAP from the base capability set Only CAP_NET_BIND_SERVICE is needed for images that listen on 80/443. CAP_SETPCAP came along as 'match Docker's defaults' but nothing here needs it, and it is the one capability in that set that exists to manipulate capabilities; keep the base set at the minimum the feature requires. The docker-in-docker runtime keeps its own, larger set. Co-authored-by: Cursor --- pkg/runtime/base_runc_config.json | 9 +++------ pkg/runtime/base_runsc_config.json | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/pkg/runtime/base_runc_config.json b/pkg/runtime/base_runc_config.json index cedbe463c..bfaf45385 100644 --- a/pkg/runtime/base_runc_config.json +++ b/pkg/runtime/base_runc_config.json @@ -31,8 +31,7 @@ "CAP_SETFCAP", "CAP_SYS_CHROOT", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_SETPCAP" + "CAP_NET_BIND_SERVICE" ], "effective": [ "CAP_AUDIT_WRITE", @@ -47,8 +46,7 @@ "CAP_SETFCAP", "CAP_SYS_CHROOT", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_SETPCAP" + "CAP_NET_BIND_SERVICE" ], "permitted": [ "CAP_AUDIT_WRITE", @@ -63,8 +61,7 @@ "CAP_SETFCAP", "CAP_SYS_CHROOT", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_SETPCAP" + "CAP_NET_BIND_SERVICE" ], "ambient": [] }, diff --git a/pkg/runtime/base_runsc_config.json b/pkg/runtime/base_runsc_config.json index 052c7783b..ceab4b482 100644 --- a/pkg/runtime/base_runsc_config.json +++ b/pkg/runtime/base_runsc_config.json @@ -31,8 +31,7 @@ "CAP_SETFCAP", "CAP_SYS_CHROOT", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_SETPCAP" + "CAP_NET_BIND_SERVICE" ], "effective": [ "CAP_AUDIT_WRITE", @@ -47,8 +46,7 @@ "CAP_SETFCAP", "CAP_SYS_CHROOT", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_SETPCAP" + "CAP_NET_BIND_SERVICE" ], "permitted": [ "CAP_AUDIT_WRITE", @@ -63,8 +61,7 @@ "CAP_SETFCAP", "CAP_SYS_CHROOT", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_SETPCAP" + "CAP_NET_BIND_SERVICE" ], "ambient": [] }, From 44210b290889f9cfc6681dfc2549cffa695ec4c9 Mon Sep 17 00:00:00 2001 From: Luke Lombardi <33990301+luke-lombardi@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:20:39 -0400 Subject: [PATCH 8/8] Drop the capability change from this branch; it gets its own PR Co-authored-by: Cursor --- pkg/runtime/base_runc_config.json | 9 +++------ pkg/runtime/base_runsc_config.json | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/pkg/runtime/base_runc_config.json b/pkg/runtime/base_runc_config.json index bfaf45385..8a9462b12 100644 --- a/pkg/runtime/base_runc_config.json +++ b/pkg/runtime/base_runc_config.json @@ -30,8 +30,7 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE" + "CAP_MKNOD" ], "effective": [ "CAP_AUDIT_WRITE", @@ -45,8 +44,7 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE" + "CAP_MKNOD" ], "permitted": [ "CAP_AUDIT_WRITE", @@ -60,8 +58,7 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE" + "CAP_MKNOD" ], "ambient": [] }, diff --git a/pkg/runtime/base_runsc_config.json b/pkg/runtime/base_runsc_config.json index ceab4b482..329960e8e 100644 --- a/pkg/runtime/base_runsc_config.json +++ b/pkg/runtime/base_runsc_config.json @@ -30,8 +30,7 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE" + "CAP_MKNOD" ], "effective": [ "CAP_AUDIT_WRITE", @@ -45,8 +44,7 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE" + "CAP_MKNOD" ], "permitted": [ "CAP_AUDIT_WRITE", @@ -60,8 +58,7 @@ "CAP_SETUID", "CAP_SETFCAP", "CAP_SYS_CHROOT", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE" + "CAP_MKNOD" ], "ambient": [] },