From 748ec2ddba97095c75592ff0fed5dc4e79047229 Mon Sep 17 00:00:00 2001 From: Alessandro Pogliaghi Date: Fri, 22 May 2026 16:57:53 +0100 Subject: [PATCH 1/2] fix(cloud): signing mcp cloud only --- .../src/main/services/agent/service.test.ts | 13 ++++++ apps/code/src/main/services/agent/service.ts | 2 + .../adapters/claude/mcp/local-tools.test.ts | 2 +- packages/agent/src/adapters/claude/types.ts | 1 + .../agent/src/adapters/codex/codex-agent.ts | 1 + .../src/adapters/local-tools/registry.test.ts | 41 ++++++++++++++----- .../src/adapters/local-tools/registry.ts | 1 + packages/agent/src/server/agent-server.ts | 1 + packages/agent/src/utils/common.ts | 13 ++++-- 9 files changed, 59 insertions(+), 16 deletions(-) diff --git a/apps/code/src/main/services/agent/service.test.ts b/apps/code/src/main/services/agent/service.test.ts index 009e4d637..8507cc607 100644 --- a/apps/code/src/main/services/agent/service.test.ts +++ b/apps/code/src/main/services/agent/service.test.ts @@ -240,6 +240,19 @@ describe("AgentService", () => { }); describe("MCP servers", () => { + it("marks desktop sessions as local even though they have a taskRunId", async () => { + await service.startSession({ + ...baseSessionParams, + adapter: "codex", + }); + + expect(mockNewSession).toHaveBeenCalledTimes(1); + expect(mockNewSession.mock.calls[0][0]._meta).toMatchObject({ + taskRunId: "run-1", + environment: "local", + }); + }); + it("passes MCP servers to newSession for codex adapter", async () => { await service.startSession({ ...baseSessionParams, diff --git a/apps/code/src/main/services/agent/service.ts b/apps/code/src/main/services/agent/service.ts index 7cf684690..e111136ce 100644 --- a/apps/code/src/main/services/agent/service.ts +++ b/apps/code/src/main/services/agent/service.ts @@ -789,6 +789,7 @@ When creating pull requests, add the following footer at the end of the PR descr persistence: { taskId, runId: taskRunId, logUrl }, }), taskRunId, + environment: "local", sessionId: existingSessionId, systemPrompt, mcpToolApprovals: toolApprovals, @@ -814,6 +815,7 @@ When creating pull requests, add the following footer at the end of the PR descr mcpServers, _meta: { taskRunId, + environment: "local", systemPrompt, mcpToolApprovals: toolApprovals, ...(permissionMode && { permissionMode }), diff --git a/packages/agent/src/adapters/claude/mcp/local-tools.test.ts b/packages/agent/src/adapters/claude/mcp/local-tools.test.ts index da6e67a3f..ad9fedaca 100644 --- a/packages/agent/src/adapters/claude/mcp/local-tools.test.ts +++ b/packages/agent/src/adapters/claude/mcp/local-tools.test.ts @@ -29,7 +29,7 @@ describe("createLocalToolsMcpServer", () => { it("exposes git_signed_commit over MCP in a cloud run with a token", async () => { const server = createLocalToolsMcpServer( { cwd: "/repo", token: "ghs_x" }, - { taskRunId: "run-1" }, + { taskRunId: "run-1", environment: "cloud" }, ); if (!server) { throw new Error("expected the local-tools server to be registered"); diff --git a/packages/agent/src/adapters/claude/types.ts b/packages/agent/src/adapters/claude/types.ts index f33e35c93..da75bb62d 100644 --- a/packages/agent/src/adapters/claude/types.ts +++ b/packages/agent/src/adapters/claude/types.ts @@ -121,6 +121,7 @@ export type SDKMessageFilter = { export type NewSessionMeta = { taskRunId?: string; taskId?: string; + environment?: "local" | "cloud"; disableBuiltInTools?: boolean; systemPrompt?: unknown; sessionId?: string; diff --git a/packages/agent/src/adapters/codex/codex-agent.ts b/packages/agent/src/adapters/codex/codex-agent.ts index 1e362151e..82746e7e5 100644 --- a/packages/agent/src/adapters/codex/codex-agent.ts +++ b/packages/agent/src/adapters/codex/codex-agent.ts @@ -97,6 +97,7 @@ export { interface NewSessionMeta { taskRunId?: string; taskId?: string; + environment?: "local" | "cloud"; systemPrompt?: string; permissionMode?: string; model?: string; diff --git a/packages/agent/src/adapters/local-tools/registry.test.ts b/packages/agent/src/adapters/local-tools/registry.test.ts index 278b7c598..4038c218d 100644 --- a/packages/agent/src/adapters/local-tools/registry.test.ts +++ b/packages/agent/src/adapters/local-tools/registry.test.ts @@ -10,7 +10,7 @@ describe("local-tools registry", () => { const savedSandbox = process.env.IS_SANDBOX; beforeEach(() => { - // isCloudRun also keys off IS_SANDBOX; clear it so meta.taskRunId is the + // isCloudRun also keys off IS_SANDBOX; clear it so meta.environment is the // only cloud signal under test. delete process.env.IS_SANDBOX; }); @@ -35,23 +35,42 @@ describe("local-tools registry", () => { }); it.each([ - { name: "cloud run with a token", taskRunId: "run-1", token: "ghs_x" }, - { name: "cloud run without a token", taskRunId: "run-1", token: undefined }, - { name: "desktop run with a token", taskRunId: undefined, token: "ghs_x" }, + { + name: "cloud run with a token", + meta: { taskRunId: "run-1", environment: "cloud" as const }, + token: "ghs_x", + expected: true, + }, + { + name: "cloud run without a token", + meta: { taskRunId: "run-1", environment: "cloud" as const }, + token: undefined, + expected: false, + }, + { + name: "desktop run with a token", + meta: { environment: "local" as const }, + token: "ghs_x", + expected: false, + }, { name: "desktop run without a token", - taskRunId: undefined, + meta: { environment: "local" as const }, token: undefined, + expected: false, + }, + { + name: "desktop run with taskRunId and token", + meta: { taskRunId: "run-1", environment: "local" as const }, + token: "ghs_x", + expected: false, }, ])( "exposes git_signed_commit only in $name when cloud+token", - ({ taskRunId, token }) => { - const tools = enabledLocalTools( - { cwd: "/repo", token }, - taskRunId ? { taskRunId } : undefined, - ); + ({ meta, token, expected }) => { + const tools = enabledLocalTools({ cwd: "/repo", token }, meta); const hasSignedCommit = tools.some((t) => t.name === "git_signed_commit"); - expect(hasSignedCommit).toBe(Boolean(taskRunId) && Boolean(token)); + expect(hasSignedCommit).toBe(expected); }, ); }); diff --git a/packages/agent/src/adapters/local-tools/registry.ts b/packages/agent/src/adapters/local-tools/registry.ts index 1f6ba9db2..0b22f1dd7 100644 --- a/packages/agent/src/adapters/local-tools/registry.ts +++ b/packages/agent/src/adapters/local-tools/registry.ts @@ -20,6 +20,7 @@ export interface LocalToolCtx { /** Minimal session-meta shape needed to gate tools (e.g. cloud-only). */ export interface LocalToolGateMeta { taskRunId?: string; + environment?: "local" | "cloud"; } /** diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index 4dad6cdd1..37ea4d8b8 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -976,6 +976,7 @@ export class AgentServer { sessionId: payload.run_id, taskRunId: payload.run_id, taskId: payload.task_id, + environment: "cloud", systemPrompt: sessionSystemPrompt, ...(this.config.model && { model: this.config.model }), allowedDomains: this.config.allowedDomains, diff --git a/packages/agent/src/utils/common.ts b/packages/agent/src/utils/common.ts index 9f100f8db..30f554231 100644 --- a/packages/agent/src/utils/common.ts +++ b/packages/agent/src/utils/common.ts @@ -27,11 +27,16 @@ export const IS_ROOT = export const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX; /** - * A cloud sandbox run, as opposed to a local desktop session. Cloud sandboxes - * always set IS_SANDBOX and carry a taskRunId; desktop sessions have neither. + * A cloud sandbox run, as opposed to a local desktop session. `taskRunId` is + * used by both desktop and cloud for persistence, so it must not imply cloud. */ -export function isCloudRun(meta: { taskRunId?: string } | undefined): boolean { - return !!process.env.IS_SANDBOX || !!meta?.taskRunId; +export function isCloudRun( + meta: { environment?: "local" | "cloud" } | undefined, +): boolean { + if (meta?.environment) { + return meta.environment === "cloud"; + } + return !!process.env.IS_SANDBOX; } /** The GitHub token available to the sandbox, if any. */ From 1199528a59d2786957b0b6c6556002790881941b Mon Sep 17 00:00:00 2001 From: Alessandro Pogliaghi Date: Fri, 22 May 2026 17:04:03 +0100 Subject: [PATCH 2/2] fix(agent): simplify local tool gate metadata --- .../adapters/claude/mcp/local-tools.test.ts | 2 +- .../src/adapters/local-tools/registry.test.ts | 18 ++++++++++-------- .../agent/src/adapters/local-tools/registry.ts | 1 - 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/agent/src/adapters/claude/mcp/local-tools.test.ts b/packages/agent/src/adapters/claude/mcp/local-tools.test.ts index ad9fedaca..d2c3fdc53 100644 --- a/packages/agent/src/adapters/claude/mcp/local-tools.test.ts +++ b/packages/agent/src/adapters/claude/mcp/local-tools.test.ts @@ -29,7 +29,7 @@ describe("createLocalToolsMcpServer", () => { it("exposes git_signed_commit over MCP in a cloud run with a token", async () => { const server = createLocalToolsMcpServer( { cwd: "/repo", token: "ghs_x" }, - { taskRunId: "run-1", environment: "cloud" }, + { environment: "cloud" }, ); if (!server) { throw new Error("expected the local-tools server to be registered"); diff --git a/packages/agent/src/adapters/local-tools/registry.test.ts b/packages/agent/src/adapters/local-tools/registry.test.ts index 4038c218d..bd76304cc 100644 --- a/packages/agent/src/adapters/local-tools/registry.test.ts +++ b/packages/agent/src/adapters/local-tools/registry.test.ts @@ -37,13 +37,13 @@ describe("local-tools registry", () => { it.each([ { name: "cloud run with a token", - meta: { taskRunId: "run-1", environment: "cloud" as const }, + meta: { environment: "cloud" as const }, token: "ghs_x", expected: true, }, { name: "cloud run without a token", - meta: { taskRunId: "run-1", environment: "cloud" as const }, + meta: { environment: "cloud" as const }, token: undefined, expected: false, }, @@ -59,12 +59,6 @@ describe("local-tools registry", () => { token: undefined, expected: false, }, - { - name: "desktop run with taskRunId and token", - meta: { taskRunId: "run-1", environment: "local" as const }, - token: "ghs_x", - expected: false, - }, ])( "exposes git_signed_commit only in $name when cloud+token", ({ meta, token, expected }) => { @@ -73,4 +67,12 @@ describe("local-tools registry", () => { expect(hasSignedCommit).toBe(expected); }, ); + + it("does not treat legacy taskRunId-only metadata as cloud", () => { + const tools = enabledLocalTools({ cwd: "/repo", token: "ghs_x" }, { + taskRunId: "run-1", + } as unknown as { environment?: "local" | "cloud" }); + const hasSignedCommit = tools.some((t) => t.name === "git_signed_commit"); + expect(hasSignedCommit).toBe(false); + }); }); diff --git a/packages/agent/src/adapters/local-tools/registry.ts b/packages/agent/src/adapters/local-tools/registry.ts index 0b22f1dd7..f308ce616 100644 --- a/packages/agent/src/adapters/local-tools/registry.ts +++ b/packages/agent/src/adapters/local-tools/registry.ts @@ -19,7 +19,6 @@ export interface LocalToolCtx { /** Minimal session-meta shape needed to gate tools (e.g. cloud-only). */ export interface LocalToolGateMeta { - taskRunId?: string; environment?: "local" | "cloud"; }