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, - ), + ); + }, });