From 93dd0a0fbeef2c675d656f4652cc357da5a655d6 Mon Sep 17 00:00:00 2001 From: Vojta Bartos Date: Fri, 22 May 2026 22:47:43 +0200 Subject: [PATCH] fix(cloud): keep git_signed_commit visible when GH token lands post-spawn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tool was gated on `ctx.token` at session-creation time, which captured `process.env.GH_TOKEN`/`GITHUB_TOKEN` once. If the orchestrator (or a shell init) set the token after the agent process started, the gate failed and the tool was filtered out of the in-process MCP server for the rest of the session — leaving the agent unable to commit at all. Drop the token check from the gate and resolve the token lazily inside the handler. The tool now stays visible in every cloud run; if no token is found at call time it returns a clear error instead of silently disappearing. --- .../src/adapters/local-tools/registry.test.ts | 19 +++++------ .../local-tools/tools/signed-commit.ts | 33 ++++++++++++++----- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/agent/src/adapters/local-tools/registry.test.ts b/packages/agent/src/adapters/local-tools/registry.test.ts index bd76304cc..c49d0a018 100644 --- a/packages/agent/src/adapters/local-tools/registry.test.ts +++ b/packages/agent/src/adapters/local-tools/registry.test.ts @@ -42,10 +42,10 @@ describe("local-tools registry", () => { expected: true, }, { - name: "cloud run without a token", + name: "cloud run without a token (resolved lazily at call time)", meta: { environment: "cloud" as const }, token: undefined, - expected: false, + expected: true, }, { name: "desktop run with a token", @@ -59,17 +59,14 @@ describe("local-tools registry", () => { token: undefined, expected: false, }, - ])( - "exposes git_signed_commit only in $name when cloud+token", - ({ meta, token, expected }) => { - const tools = enabledLocalTools({ cwd: "/repo", token }, meta); - const hasSignedCommit = tools.some((t) => t.name === "git_signed_commit"); - expect(hasSignedCommit).toBe(expected); - }, - ); + ])("exposes git_signed_commit in $name", ({ meta, token, expected }) => { + const tools = enabledLocalTools({ cwd: "/repo", token }, meta); + const hasSignedCommit = tools.some((t) => t.name === "git_signed_commit"); + expect(hasSignedCommit).toBe(expected); + }); it("does not treat legacy taskRunId-only metadata as cloud", () => { - const tools = enabledLocalTools({ cwd: "/repo", token: "ghs_x" }, { + const tools = enabledLocalTools({ cwd: "/repo", token: undefined }, { taskRunId: "run-1", } as unknown as { environment?: "local" | "cloud" }); const hasSignedCommit = tools.some((t) => t.name === "git_signed_commit"); diff --git a/packages/agent/src/adapters/local-tools/tools/signed-commit.ts b/packages/agent/src/adapters/local-tools/tools/signed-commit.ts index 2a9df9680..24b78e5f0 100644 --- a/packages/agent/src/adapters/local-tools/tools/signed-commit.ts +++ b/packages/agent/src/adapters/local-tools/tools/signed-commit.ts @@ -1,4 +1,4 @@ -import { isCloudRun } from "../../../utils/common"; +import { isCloudRun, resolveGithubToken } from "../../../utils/common"; import { runSignedCommitTool, SIGNED_COMMIT_TOOL_DESCRIPTION, @@ -8,19 +8,34 @@ import { import { defineLocalTool } from "../registry"; /** - * `git_signed_commit` as a local tool. Cloud runs only, and only when a GitHub - * token is available (the commit is created via GitHub's API, which also signs - * it). Committing is core to cloud tasks, so keep it visible past ToolSearch. + * `git_signed_commit` as a local tool. Cloud-run only; the token is resolved + * lazily at call time so the tool stays visible even when the GitHub token + * lands in `process.env` after the session was created (e.g. an orchestrator + * injecting it post-spawn). Committing is core to cloud tasks, so keep it + * exposed past ToolSearch via `alwaysLoad`. */ export const signedCommitTool = defineLocalTool({ name: SIGNED_COMMIT_TOOL_NAME, description: SIGNED_COMMIT_TOOL_DESCRIPTION, schema: signedCommitToolSchema, alwaysLoad: true, - isEnabled: (ctx, meta) => isCloudRun(meta) && !!ctx.token, - handler: (ctx, args) => - runSignedCommitTool( - { cwd: ctx.cwd, token: ctx.token ?? "", taskId: ctx.taskId }, + isEnabled: (_ctx, meta) => isCloudRun(meta), + handler: (ctx, args) => { + const token = ctx.token ?? resolveGithubToken(); + if (!token) { + return Promise.resolve({ + content: [ + { + type: "text" as const, + text: `${SIGNED_COMMIT_TOOL_NAME} failed: no GitHub token in env (GH_TOKEN/GITHUB_TOKEN)`, + }, + ], + isError: true, + }); + } + return runSignedCommitTool( + { cwd: ctx.cwd, token, taskId: ctx.taskId }, args, - ), + ); + }, });