From 9e0d673a4b0b766789ec707174f54d06aa4ec0ad Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 24 Jun 2026 19:42:41 -0500 Subject: [PATCH] chore: retire the dead run-sandbox-command task (#1815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item 2 of recoupable/chat#1815. After the chat#1813 cutover, nothing triggers the OpenClaw `run-sandbox-command` task — the scheduled path runs on the api's runAgentWorkflow. Removes it + the code used ONLY by it: - src/tasks/runSandboxCommandTask.ts (id "run-sandbox-command"; no caller — the only reference was a stale comment in getSandboxEnv) - src/schemas/sandboxSchema.ts (its exclusive payload schema) - src/sandboxes/appendNoCommitInstruction.ts + git/syncOrgRepos.ts (+ tests) — reverse-dep checked: imported only by runSandboxCommandTask Scope correction vs the issue's initial guess: the broader OpenClaw code is NOT dormant. installOpenClaw / setupOpenClaw / runOpenClawAgent / provisionSandbox / getSandboxEnv / OPENCLAW_DEFAULT_MODEL etc. are all still LIVE via the background coding-agent product (codingAgentTask, setupSandboxTask, updatePRTask) — they stay. Only the run-sandbox-command task + its exclusive helpers go. Verified: grep clean for the deleted symbols; full suite green (355 tests). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../appendNoCommitInstruction.test.ts | 49 ------- src/sandboxes/__tests__/syncOrgRepos.test.ts | 72 ----------- src/sandboxes/appendNoCommitInstruction.ts | 28 ---- src/sandboxes/getSandboxEnv.ts | 2 +- src/sandboxes/git/syncOrgRepos.ts | 46 ------- src/schemas/sandboxSchema.ts | 29 ----- src/tasks/runSandboxCommandTask.ts | 122 ------------------ 7 files changed, 1 insertion(+), 347 deletions(-) delete mode 100644 src/sandboxes/__tests__/appendNoCommitInstruction.test.ts delete mode 100644 src/sandboxes/__tests__/syncOrgRepos.test.ts delete mode 100644 src/sandboxes/appendNoCommitInstruction.ts delete mode 100644 src/sandboxes/git/syncOrgRepos.ts delete mode 100644 src/schemas/sandboxSchema.ts delete mode 100644 src/tasks/runSandboxCommandTask.ts diff --git a/src/sandboxes/__tests__/appendNoCommitInstruction.test.ts b/src/sandboxes/__tests__/appendNoCommitInstruction.test.ts deleted file mode 100644 index 54d9925..0000000 --- a/src/sandboxes/__tests__/appendNoCommitInstruction.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { appendNoCommitInstruction, NO_COMMIT_INSTRUCTION } from "../appendNoCommitInstruction"; - -describe("appendNoCommitInstruction", () => { - it("appends the instruction to the value after --message", () => { - const result = appendNoCommitInstruction([ - "agent", - "--agent", - "main", - "--message", - "fix the bug", - ]); - - expect(result).toEqual([ - "agent", - "--agent", - "main", - "--message", - `fix the bug ${NO_COMMIT_INSTRUCTION}`, - ]); - }); - - it("is idempotent — does not re-append when the instruction is already present", () => { - const original = [ - "agent", - "--message", - `do something ${NO_COMMIT_INSTRUCTION}`, - ]; - - expect(appendNoCommitInstruction(original)).toEqual(original); - }); - - it("leaves args unchanged when there is no --message flag", () => { - const original = ["status"]; - expect(appendNoCommitInstruction(original)).toEqual(original); - }); - - it("leaves args unchanged when --message is the last arg (no value)", () => { - const original = ["agent", "--message"]; - expect(appendNoCommitInstruction(original)).toEqual(original); - }); - - it("returns a new array rather than mutating the input", () => { - const original = ["--message", "hi"]; - const result = appendNoCommitInstruction(original); - expect(result).not.toBe(original); - expect(original).toEqual(["--message", "hi"]); - }); -}); diff --git a/src/sandboxes/__tests__/syncOrgRepos.test.ts b/src/sandboxes/__tests__/syncOrgRepos.test.ts deleted file mode 100644 index 1dc82d5..0000000 --- a/src/sandboxes/__tests__/syncOrgRepos.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -vi.mock("@trigger.dev/sdk/v3", () => ({ - logger: { log: vi.fn(), error: vi.fn() }, - metadata: { set: vi.fn(), append: vi.fn() }, -})); - -const { syncOrgRepos } = await import("../git/syncOrgRepos"); - -function createMockSandbox() { - const runCommand = vi.fn().mockResolvedValue({ - wait: vi.fn().mockResolvedValue({ - exitCode: 0, - stdout: async () => "", - stderr: async () => "", - }), - exitCode: 0, - stdout: async () => "", - stderr: async () => "", - }); - - return { runCommand } as any; -} - -beforeEach(() => { - vi.clearAllMocks(); - process.env.GITHUB_TOKEN = "test-token"; -}); - -describe("syncOrgRepos", () => { - it("skips when no GITHUB_TOKEN", async () => { - delete process.env.GITHUB_TOKEN; - const sandbox = createMockSandbox(); - - await syncOrgRepos(sandbox); - - expect(sandbox.runCommand).not.toHaveBeenCalled(); - }); - - it("runs an openclaw agent prompt to sync org repos", async () => { - const sandbox = createMockSandbox(); - - await syncOrgRepos(sandbox); - - const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" - ); - expect(openclawCall).toBeDefined(); - - const args = openclawCall![0].args; - const message = args.find( - (a: string, i: number) => args[i - 1] === "--message" - ); - expect(message).toContain("git fetch origin main"); - expect(message).toContain("git reset --hard origin/main"); - }); - - it("instructs OpenClaw to handle .git files (submodule gitlinks)", async () => { - const sandbox = createMockSandbox(); - - await syncOrgRepos(sandbox); - - const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" - ); - const args = openclawCall![0].args; - const message = args.find( - (a: string, i: number) => args[i - 1] === "--message" - ); - expect(message).toContain(".git file"); - }); -}); diff --git a/src/sandboxes/appendNoCommitInstruction.ts b/src/sandboxes/appendNoCommitInstruction.ts deleted file mode 100644 index 8700ddf..0000000 --- a/src/sandboxes/appendNoCommitInstruction.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NO_COMMIT_INSTRUCTION = "do not commit your changes."; - -/** - * Hotfix guardrail: ensure the `--message` value passed to `openclaw agent` - * (or any tool that takes `--message `) ends with the - * {@link NO_COMMIT_INSTRUCTION} text. The task runs in a sandbox that is - * git-aware, and we push sandbox changes to GitHub after the command — we - * do NOT want the agent itself creating commits during the run. - * - * @param args - The `args` payload passed to `sandbox.runCommand`. - * @returns A new args array with the instruction appended to the `--message` - * value if one is present and the instruction isn't already there. - * Otherwise the input is returned unchanged (as a copy). - */ -export function appendNoCommitInstruction(args: readonly string[]): string[] { - const out = [...args]; - const messageIdx = out.indexOf("--message"); - if (messageIdx === -1) return out; - - const valueIdx = messageIdx + 1; - if (valueIdx >= out.length) return out; - - const current = out[valueIdx]; - if (current.includes(NO_COMMIT_INSTRUCTION)) return out; - - out[valueIdx] = `${current} ${NO_COMMIT_INSTRUCTION}`; - return out; -} diff --git a/src/sandboxes/getSandboxEnv.ts b/src/sandboxes/getSandboxEnv.ts index 3906254..3b2689f 100644 --- a/src/sandboxes/getSandboxEnv.ts +++ b/src/sandboxes/getSandboxEnv.ts @@ -1,6 +1,6 @@ /** * Builds the environment variables to inject into sandbox commands. - * Shared by runSandboxCommandTask and codingAgentTask. + * Used by codingAgentTask and updatePRTask. */ export function getSandboxEnv( accountId: string diff --git a/src/sandboxes/git/syncOrgRepos.ts b/src/sandboxes/git/syncOrgRepos.ts deleted file mode 100644 index ead4f6b..0000000 --- a/src/sandboxes/git/syncOrgRepos.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Sandbox } from "@vercel/sandbox"; -import { runOpenClawAgent } from "../runOpenClawAgent"; -import { logStep } from "../logStep"; - -/** - * Syncs all org repos in the sandbox workspace to their latest remote state - * via an OpenClaw agent prompt. - * - * This ensures org repos are up-to-date with their remote `main` branch - * before any commands run, preventing stale snapshots from causing - * force-pushes that delete already-merged commits. - * - * Must be called AFTER `ensureOrgRepos` and BEFORE running commands. - * - * @param sandbox - The Vercel Sandbox instance - */ -export async function syncOrgRepos(sandbox: Sandbox): Promise { - const githubToken = process.env.GITHUB_TOKEN; - - if (!githubToken) { - logStep("No GITHUB_TOKEN, skipping org repo sync", false); - return; - } - - logStep("Syncing org repos"); - - const message = [ - "Sync all org repos to the latest remote state.", - "Org repos are at ~/.openclaw/workspace/orgs/", - "", - "For each org directory that is a git repo (has a .git directory or .git file):", - "1. git fetch origin main", - "2. git reset --hard origin/main", - "", - "This ensures we have the latest commits before making any changes.", - "If there are no org repos, that's fine — just skip.", - "Continue to the next repo if one fails.", - ].join("\n"); - - await runOpenClawAgent(sandbox, { - label: "Syncing org repos to latest remote", - message, - }); - - logStep("Org repos synced", false); -} diff --git a/src/schemas/sandboxSchema.ts b/src/schemas/sandboxSchema.ts deleted file mode 100644 index 943eeea..0000000 --- a/src/schemas/sandboxSchema.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod"; - -export const runSandboxCommandPayloadSchema = z.object({ - command: z.string().min(1, "command is required"), - args: z.array(z.string()).optional(), - cwd: z.string().optional(), - sandboxId: z.string().min(1, "sandboxId is required"), - accountId: z.string().min(1, "accountId is required"), -}); - -export type RunSandboxCommandPayload = z.infer< - typeof runSandboxCommandPayloadSchema ->; - -export const snapshotSchema = z.object({ - id: z.string(), - expiresAt: z.string(), -}); - -export type Snapshot = z.infer; - -export const sandboxResultSchema = z.object({ - stdout: z.string(), - stderr: z.string(), - exitCode: z.number(), - snapshot: snapshotSchema, -}); - -export type SandboxResult = z.infer; diff --git a/src/tasks/runSandboxCommandTask.ts b/src/tasks/runSandboxCommandTask.ts deleted file mode 100644 index 673d27c..0000000 --- a/src/tasks/runSandboxCommandTask.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { logger, schemaTask, tags } from "@trigger.dev/sdk/v3"; -import { logStep } from "../sandboxes/logStep"; -import { Sandbox } from "@vercel/sandbox"; -import { installOpenClaw } from "../sandboxes/installOpenClaw"; -import { setupOpenClaw } from "../sandboxes/setupOpenClaw"; -import { ensureGithubRepo } from "../sandboxes/ensureGithubRepo"; -import { getVercelSandboxCredentials } from "../sandboxes/getVercelSandboxCredentials"; -import { snapshotAndPersist } from "../sandboxes/snapshotAndPersist"; -import { getSandboxEnv } from "../sandboxes/getSandboxEnv"; -import { writeReadme } from "../sandboxes/writeReadme"; -import { ensureOrgRepos } from "../sandboxes/ensureOrgRepos"; -import { ensureSetupSandbox } from "../sandboxes/ensureSetupSandbox"; -import { pushSandboxToGithub } from "../sandboxes/pushSandboxToGithub"; -import { syncOrgRepos } from "../sandboxes/git/syncOrgRepos"; -import { appendNoCommitInstruction } from "../sandboxes/appendNoCommitInstruction"; -import { - runSandboxCommandPayloadSchema, - type SandboxResult, -} from "../schemas/sandboxSchema"; - -/** - * Background task that connects to an existing Vercel Sandbox, ensures OpenClaw - * is installed with AI Gateway, runs a command with arguments, captures - * output, takes a snapshot, and updates the account's snapshot ID. - */ -export const runSandboxCommandTask = schemaTask({ - id: "run-sandbox-command", - schema: runSandboxCommandPayloadSchema, - maxDuration: 60 * 15, // 15 minutes max for sandbox execution - retry: { - maxAttempts: 1, // No retries - sandbox operations are not idempotent - }, - run: async (payload): Promise => { - const { command, args, cwd, sandboxId, accountId } = payload; - await tags.add(`account:${accountId}`); - const { token, teamId, projectId } = getVercelSandboxCredentials(); - - logger.log("Starting sandbox command execution", { - sandboxId, - command, - args, - cwd, - accountId, - }); - - const sandbox = await Sandbox.get({ name: sandboxId, token, teamId, projectId }); - - logStep("Connected to sandbox"); - - try { - // Ensure OpenClaw is installed and configured with AI Gateway - await installOpenClaw(sandbox); - await setupOpenClaw(sandbox, accountId); - logStep("OpenClaw onboard complete, starting gateway"); - - // Ensure GitHub repo exists and is cloned in sandbox - const githubRepo = await ensureGithubRepo(sandbox, accountId); - logStep("GitHub repo ready"); - - // Write README.md with sandbox details - await writeReadme(sandbox, sandboxId, accountId, githubRepo ?? undefined); - logStep("README written", false); - - // Ensure org GitHub repos exist and are cloned in workspace - await ensureOrgRepos(sandbox, accountId); - - // Ensure org/artist folder structure exists (setup via OpenClaw) - await ensureSetupSandbox(sandbox, accountId); - - // Sync all org repos to latest remote state before running commands. - // Prevents stale snapshots from causing force-pushes that delete - // already-merged commits. - await syncOrgRepos(sandbox); - - // Run the command with args - logStep("Running command"); - - const commandResult = await sandbox.runCommand({ - cmd: command, - args: appendNoCommitInstruction(args || []), - cwd, - env: getSandboxEnv(accountId), - }); - - const stdout = (await commandResult.stdout()) || ""; - const stderr = (await commandResult.stderr()) || ""; - const exitCode = commandResult.exitCode; - - logStep("Command execution completed", false); - - // Push sandbox files to GitHub repo - logStep("Pushing to GitHub"); - await pushSandboxToGithub(sandbox); - - const snapshotResult = await snapshotAndPersist( - sandbox, - accountId, - githubRepo ?? undefined - ); - - const result: SandboxResult = { - stdout, - stderr, - exitCode, - snapshot: { - id: snapshotResult.snapshotId, - expiresAt: snapshotResult.expiresAt.toISOString(), - }, - }; - - logStep("Sandbox command completed successfully"); - - return result; - } catch (error) { - logStep("Failed"); - logger.error("Sandbox command failed", { - error: error instanceof Error ? error.message : String(error), - }); - throw error; - } - }, -});