From 201c879772df4724350f0c6231b393298efcb230 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 9 Jan 2026 13:29:47 +0100 Subject: [PATCH] fix(sandbox): compare list to config images (#563) - thanks @pasogott --- src/agents/sandbox.ts | 30 ++++++++++++++++++++++++++---- src/commands/sandbox.test.ts | 4 ++++ src/commands/sandbox.ts | 10 ++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/agents/sandbox.ts b/src/agents/sandbox.ts index e5c11ed27..d9121c93a 100644 --- a/src/agents/sandbox.ts +++ b/src/agents/sandbox.ts @@ -14,8 +14,12 @@ import { resolveProfile, } from "../browser/config.js"; import { DEFAULT_CLAWD_BROWSER_COLOR } from "../browser/constants.js"; -import type { ClawdbotConfig } from "../config/config.js"; -import { STATE_DIR_CLAWDBOT } from "../config/config.js"; +import { + type ClawdbotConfig, + loadConfig, + STATE_DIR_CLAWDBOT, +} from "../config/config.js"; +import { normalizeAgentId } from "../routing/session-key.js"; import { defaultRuntime } from "../runtime.js"; import { resolveUserPath } from "../utils.js"; import { resolveAgentIdFromSessionKey } from "./agent-scope.js"; @@ -329,6 +333,14 @@ function resolveSandboxScopeKey(scope: SandboxScope, sessionKey: string) { return `agent:${agentId}`; } +function resolveSandboxAgentId(scopeKey: string): string | undefined { + const trimmed = scopeKey.trim(); + if (!trimmed || trimmed === "shared") return undefined; + const parts = trimmed.split(":").filter(Boolean); + if (parts[0] === "agent" && parts[1]) return normalizeAgentId(parts[1]); + return resolveAgentIdFromSessionKey(trimmed); +} + export function resolveSandboxConfigForAgent( cfg?: ClawdbotConfig, agentId?: string, @@ -1159,6 +1171,7 @@ export type SandboxBrowserInfo = SandboxBrowserRegistryEntry & { }; export async function listSandboxContainers(): Promise { + const config = loadConfig(); const registry = await readRegistry(); const results: SandboxContainerInfo[] = []; @@ -1179,10 +1192,14 @@ export async function listSandboxContainers(): Promise { // ignore } } + const agentId = resolveSandboxAgentId(entry.sessionKey); + const configuredImage = resolveSandboxConfigForAgent(config, agentId).docker + .image; results.push({ ...entry, + image: actualImage, running: state.running, - imageMatch: actualImage === entry.image, + imageMatch: actualImage === configuredImage, }); } @@ -1190,6 +1207,7 @@ export async function listSandboxContainers(): Promise { } export async function listSandboxBrowsers(): Promise { + const config = loadConfig(); const registry = await readBrowserRegistry(); const results: SandboxBrowserInfo[] = []; @@ -1209,10 +1227,14 @@ export async function listSandboxBrowsers(): Promise { // ignore } } + const agentId = resolveSandboxAgentId(entry.sessionKey); + const configuredImage = resolveSandboxConfigForAgent(config, agentId) + .browser.image; results.push({ ...entry, + image: actualImage, running: state.running, - imageMatch: actualImage === entry.image, + imageMatch: actualImage === configuredImage, }); } diff --git a/src/commands/sandbox.test.ts b/src/commands/sandbox.test.ts index 63d54ed5d..7c51f97ea 100644 --- a/src/commands/sandbox.test.ts +++ b/src/commands/sandbox.test.ts @@ -221,6 +221,8 @@ describe("sandboxRecreateCommand", () => { "Please specify --all, --session , or --agent ", ); expect(runtime.exit).toHaveBeenCalledWith(1); + expect(mocks.listSandboxContainers).not.toHaveBeenCalled(); + expect(mocks.listSandboxBrowsers).not.toHaveBeenCalled(); }); it("should error if multiple filters specified", async () => { @@ -234,6 +236,8 @@ describe("sandboxRecreateCommand", () => { "Please specify only one of: --all, --session, --agent", ); expect(runtime.exit).toHaveBeenCalledWith(1); + expect(mocks.listSandboxContainers).not.toHaveBeenCalled(); + expect(mocks.listSandboxBrowsers).not.toHaveBeenCalled(); }); }); diff --git a/src/commands/sandbox.ts b/src/commands/sandbox.ts index 058b0305f..b2dd2f342 100644 --- a/src/commands/sandbox.ts +++ b/src/commands/sandbox.ts @@ -72,7 +72,9 @@ export async function sandboxRecreateCommand( opts: SandboxRecreateOptions, runtime: RuntimeEnv, ): Promise { - validateRecreateOptions(opts, runtime); + if (!validateRecreateOptions(opts, runtime)) { + return; + } const filtered = await fetchAndFilterContainers(opts); @@ -101,10 +103,11 @@ export async function sandboxRecreateCommand( function validateRecreateOptions( opts: SandboxRecreateOptions, runtime: RuntimeEnv, -): void { +): boolean { if (!opts.all && !opts.session && !opts.agent) { runtime.error("Please specify --all, --session , or --agent "); runtime.exit(1); + return false; } const exclusiveCount = [opts.all, opts.session, opts.agent].filter( @@ -113,7 +116,10 @@ function validateRecreateOptions( if (exclusiveCount > 1) { runtime.error("Please specify only one of: --all, --session, --agent"); runtime.exit(1); + return false; } + + return true; } // --- Filtering ---