mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 03:00:48 +00:00
fix(foundry): use $HOME instead of hardcoded /home/sandbox for sandbox repo paths
E2B sandboxes run as `user` (home: /home/user), not `sandbox`, so `mkdir -p /home/sandbox` fails with "Permission denied". Replace all hardcoded `/home/sandbox` paths with `$HOME` resolved at shell runtime inside the sandbox, and dynamically resolve the repo CWD via the sandbox actor so it works across providers (E2B, local Docker, Daytona). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ffb9f1082b
commit
bea3b58199
2 changed files with 65 additions and 19 deletions
|
|
@ -13,7 +13,12 @@ import { logActorWarning, resolveErrorMessage } from "../logging.js";
|
|||
import { expectQueueResponse } from "../../services/queue.js";
|
||||
import { resolveSandboxProviderId } from "../../sandbox-config.js";
|
||||
|
||||
const SANDBOX_REPO_CWD = "/home/sandbox/repo";
|
||||
/**
|
||||
* Default repo CWD inside the sandbox. The actual path is resolved dynamically
|
||||
* via `$HOME/repo` because different sandbox providers run as different users
|
||||
* (e.g. E2B uses `/home/user`, local Docker uses `/home/sandbox`).
|
||||
*/
|
||||
const DEFAULT_SANDBOX_REPO_CWD = "/home/user/repo";
|
||||
const DEFAULT_LOCAL_SANDBOX_IMAGE = "rivetdev/sandbox-agent:foundry-base-latest";
|
||||
const DEFAULT_LOCAL_SANDBOX_PORT = 2468;
|
||||
const dockerClient = new Dockerode({ socketPath: "/var/run/docker.sock" });
|
||||
|
|
@ -297,6 +302,43 @@ async function listWorkspaceModelGroupsForSandbox(c: any): Promise<WorkspaceMode
|
|||
|
||||
const baseActions = baseTaskSandbox.config.actions as Record<string, (c: any, ...args: any[]) => Promise<any>>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dynamic repo CWD resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let cachedRepoCwd: string | null = null;
|
||||
|
||||
/**
|
||||
* Resolve the repo CWD inside the sandbox by querying `$HOME`.
|
||||
* Different providers run as different users (E2B: `/home/user`, local Docker:
|
||||
* `/home/sandbox`), so the path must be resolved dynamically. The result is
|
||||
* cached for the lifetime of this sandbox actor instance.
|
||||
*/
|
||||
async function resolveRepoCwd(c: any): Promise<string> {
|
||||
if (cachedRepoCwd) return cachedRepoCwd;
|
||||
|
||||
try {
|
||||
const result = await baseActions.runProcess(c, {
|
||||
command: "bash",
|
||||
args: ["-lc", "echo $HOME"],
|
||||
cwd: "/",
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
const home = (result.stdout ?? result.result ?? "").trim();
|
||||
if (home && home.startsWith("/")) {
|
||||
cachedRepoCwd = `${home}/repo`;
|
||||
return cachedRepoCwd;
|
||||
}
|
||||
} catch (error) {
|
||||
logActorWarning("taskSandbox", "failed to resolve $HOME, using default", {
|
||||
error: resolveErrorMessage(error),
|
||||
});
|
||||
}
|
||||
|
||||
cachedRepoCwd = DEFAULT_SANDBOX_REPO_CWD;
|
||||
return cachedRepoCwd;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queue names for sandbox actor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -528,8 +570,9 @@ export const taskSandbox = actor({
|
|||
}
|
||||
},
|
||||
|
||||
async repoCwd(): Promise<{ cwd: string }> {
|
||||
return { cwd: SANDBOX_REPO_CWD };
|
||||
async repoCwd(c: any): Promise<{ cwd: string }> {
|
||||
const resolved = await resolveRepoCwd(c);
|
||||
return { cwd: resolved };
|
||||
},
|
||||
|
||||
// Long-running action — kept as direct action to avoid blocking the
|
||||
|
|
@ -600,4 +643,4 @@ export const taskSandbox = actor({
|
|||
run: workflow(runSandboxWorkflow),
|
||||
});
|
||||
|
||||
export { SANDBOX_REPO_CWD };
|
||||
export { DEFAULT_SANDBOX_REPO_CWD, resolveRepoCwd };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// @ts-nocheck
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { basename, dirname } from "node:path";
|
||||
import { basename } from "node:path";
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import {
|
||||
DEFAULT_WORKSPACE_MODEL_GROUPS,
|
||||
|
|
@ -11,7 +11,6 @@ import {
|
|||
import { getActorRuntimeContext } from "../context.js";
|
||||
import { getOrCreateOrganization, getOrCreateTaskSandbox, getOrCreateUser, getTaskSandbox, selfTask } from "../handles.js";
|
||||
import { logActorInfo, logActorWarning, resolveErrorMessage } from "../logging.js";
|
||||
import { SANDBOX_REPO_CWD } from "../sandbox/index.js";
|
||||
import { resolveSandboxProviderId } from "../../sandbox-config.js";
|
||||
import { getBetterAuthService } from "../../services/better-auth.js";
|
||||
import { resolveOrganizationGithubAuth } from "../../services/github-auth.js";
|
||||
|
|
@ -183,9 +182,9 @@ async function injectGitCredentials(sandbox: any, login: string, email: string,
|
|||
"set -euo pipefail",
|
||||
`git config --global user.name ${JSON.stringify(login)}`,
|
||||
`git config --global user.email ${JSON.stringify(email)}`,
|
||||
`git config --global credential.helper 'store --file=/home/sandbox/.git-token'`,
|
||||
`printf '%s\\n' ${JSON.stringify(`https://${login}:${token}@github.com`)} > /home/sandbox/.git-token`,
|
||||
`chmod 600 /home/sandbox/.git-token`,
|
||||
`git config --global credential.helper 'store --file=$HOME/.git-token'`,
|
||||
`printf '%s\\n' ${JSON.stringify(`https://${login}:${token}@github.com`)} > $HOME/.git-token`,
|
||||
`chmod 600 $HOME/.git-token`,
|
||||
];
|
||||
const result = await sandbox.runProcess({
|
||||
command: "bash",
|
||||
|
|
@ -576,6 +575,10 @@ async function getTaskSandboxRuntime(
|
|||
const sandbox = await getOrCreateTaskSandbox(c, c.state.organizationId, sandboxId, {});
|
||||
const actorId = typeof sandbox.resolve === "function" ? await sandbox.resolve().catch(() => null) : null;
|
||||
const switchTarget = sandboxProviderId === "local" ? `sandbox://local/${sandboxId}` : `sandbox://e2b/${sandboxId}`;
|
||||
|
||||
// Resolve the actual repo CWD from the sandbox's $HOME (differs by provider).
|
||||
const repoCwdResult = await sandbox.repoCwd();
|
||||
const cwd = repoCwdResult?.cwd ?? "$HOME/repo";
|
||||
const now = Date.now();
|
||||
|
||||
await c.db
|
||||
|
|
@ -585,7 +588,7 @@ async function getTaskSandboxRuntime(
|
|||
sandboxProviderId,
|
||||
sandboxActorId: typeof actorId === "string" ? actorId : null,
|
||||
switchTarget,
|
||||
cwd: SANDBOX_REPO_CWD,
|
||||
cwd,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
|
|
@ -595,7 +598,7 @@ async function getTaskSandboxRuntime(
|
|||
sandboxProviderId,
|
||||
sandboxActorId: typeof actorId === "string" ? actorId : null,
|
||||
switchTarget,
|
||||
cwd: SANDBOX_REPO_CWD,
|
||||
cwd,
|
||||
updatedAt: now,
|
||||
},
|
||||
})
|
||||
|
|
@ -606,7 +609,7 @@ async function getTaskSandboxRuntime(
|
|||
.set({
|
||||
activeSandboxId: sandboxId,
|
||||
activeSwitchTarget: switchTarget,
|
||||
activeCwd: SANDBOX_REPO_CWD,
|
||||
activeCwd: cwd,
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(taskRuntime.id, 1))
|
||||
|
|
@ -617,7 +620,7 @@ async function getTaskSandboxRuntime(
|
|||
sandboxId,
|
||||
sandboxProviderId,
|
||||
switchTarget,
|
||||
cwd: SANDBOX_REPO_CWD,
|
||||
cwd,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -648,15 +651,15 @@ async function ensureSandboxRepo(c: any, sandbox: any, record: any, opts?: { ski
|
|||
logActorInfo("task.sandbox", "resolveAuth+metadata", { durationMs: Math.round(performance.now() - t0) });
|
||||
|
||||
const baseRef = metadata.defaultBranch ?? "main";
|
||||
const sandboxRepoRoot = dirname(SANDBOX_REPO_CWD);
|
||||
// Use $HOME inside the shell script so the path resolves correctly regardless
|
||||
// of which user the sandbox runs as (E2B: "user", local Docker: "sandbox").
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
`mkdir -p ${JSON.stringify(sandboxRepoRoot)}`,
|
||||
'REPO_DIR="$HOME/repo"',
|
||||
'mkdir -p "$HOME"',
|
||||
"git config --global credential.helper '!f() { echo username=x-access-token; echo password=${GH_TOKEN:-$GITHUB_TOKEN}; }; f'",
|
||||
`if [ ! -d ${JSON.stringify(`${SANDBOX_REPO_CWD}/.git`)} ]; then rm -rf ${JSON.stringify(SANDBOX_REPO_CWD)} && git clone ${JSON.stringify(
|
||||
metadata.remoteUrl,
|
||||
)} ${JSON.stringify(SANDBOX_REPO_CWD)}; fi`,
|
||||
`cd ${JSON.stringify(SANDBOX_REPO_CWD)}`,
|
||||
`if [ ! -d "$REPO_DIR/.git" ]; then rm -rf "$REPO_DIR" && git clone ${JSON.stringify(metadata.remoteUrl)} "$REPO_DIR"; fi`,
|
||||
'cd "$REPO_DIR"',
|
||||
"git fetch origin --prune",
|
||||
`if git show-ref --verify --quiet refs/remotes/origin/${JSON.stringify(record.branchName).slice(1, -1)}; then target_ref=${JSON.stringify(
|
||||
`origin/${record.branchName}`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue