fix: Better daytona error handling (#429)

This commit is contained in:
Brace Sproul 2025-07-16 13:09:00 -07:00 • committed by GitHub
parent 13329fe5c1
commit d4ca73089c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 135 additions and 33 deletions

View file

@ -22,7 +22,10 @@ import {
import { Command } from "@langchain/langgraph";
import { truncateOutput } from "../../../utils/truncate-outputs.js";
import { getSandboxWithErrorHandling } from "../../../utils/sandbox.js";
import { getCodebaseTree } from "../../../utils/tree.js";
import {
FAILED_TO_GENERATE_TREE_MESSAGE,
getCodebaseTree,
} from "../../../utils/tree.js";
import { getRepoAbsolutePath } from "@open-swe/shared/git";
import { createInstallDependenciesTool } from "../../../tools/install-dependencies.js";
import { createSearchTool } from "../../../tools/search.js";
@ -180,6 +183,11 @@ export async function takeAction(
]);
const codebaseTree = await getCodebaseTree();
// If the codebase tree failed to generate, fallback to the previous codebase tree, or if that's not defined, use the failed to generate message.
const codebaseTreeToReturn =
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE
? (state.codebaseTree ?? codebaseTree)
: codebaseTree;
// Prioritize wereDependenciesInstalled over dependenciesInstalled
const dependenciesInstalledUpdate =
@ -193,7 +201,7 @@ export async function takeAction(
messages: toolCallResults,
internalMessages: toolCallResults,
...(branchName && { branchName }),
codebaseTree,
codebaseTree: codebaseTreeToReturn,
sandboxSessionId: sandbox.id,
...(dependenciesInstalledUpdate !== null && {
dependenciesInstalled: dependenciesInstalledUpdate,

View file

@ -14,7 +14,10 @@ import {
configureGitUserInRepo,
pullLatestChanges,
} from "../../utils/github/git.js";
import { getCodebaseTree } from "../../utils/tree.js";
import {
FAILED_TO_GENERATE_TREE_MESSAGE,
getCodebaseTree,
} from "../../utils/tree.js";
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
import {
CustomNodeEvent,
@ -171,7 +174,15 @@ export async function initializeSandbox(
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
try {
const codebaseTree = await getCodebaseTree(existingSandbox.id);
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
if (codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE) {
emitStepEvent(
baseGenerateCodebaseTreeAction,
"error",
FAILED_TO_GENERATE_TREE_MESSAGE,
);
} else {
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
}
return {
sandboxSessionId: existingSandbox.id,
@ -183,9 +194,14 @@ export async function initializeSandbox(
emitStepEvent(
baseGenerateCodebaseTreeAction,
"error",
"Failed to generate codebase tree. Please try again later.",
FAILED_TO_GENERATE_TREE_MESSAGE,
);
throw new Error("Failed to generate codebase tree.");
return {
sandboxSessionId: existingSandbox.id,
codebaseTree: FAILED_TO_GENERATE_TREE_MESSAGE,
messages: createEventsMessage(),
customRules: await getCustomRules(existingSandbox, absoluteRepoDir),
};
}
} catch {
emitStepEvent(

View file

@ -5,7 +5,7 @@ import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../constants.js";
import { getGitHubTokensFromConfig } from "./github-tokens.js";
import { cloneRepo, configureGitUserInRepo } from "./github/git.js";
import { getRepoAbsolutePath } from "@open-swe/shared/git";
import { getCodebaseTree } from "./tree.js";
import { FAILED_TO_GENERATE_TREE_MESSAGE, getCodebaseTree } from "./tree.js";
const logger = createLogger(LogLevel.INFO, "Sandbox");
@ -65,6 +65,28 @@ export async function deleteSandbox(
}
}
async function createSandbox(attempt: number): Promise<Sandbox | null> {
try {
return await daytonaClient().create(DEFAULT_SANDBOX_CREATE_PARAMS, {
timeout: 100, // 100s timeout on creation.
});
} catch (e) {
logger.error("Failed to create sandbox", {
attempt,
...(e instanceof Error
? {
name: e.name,
message: e.message,
stack: e.stack,
}
: {
error: e,
}),
});
return null;
}
}
export async function getSandboxWithErrorHandling(
sandboxSessionId: string | undefined,
targetRepository: TargetRepository,
@ -112,7 +134,19 @@ export async function getSandboxWithErrorHandling(
error,
});
const sandbox = await daytonaClient().create(DEFAULT_SANDBOX_CREATE_PARAMS);
let sandbox: Sandbox | null = null;
let numSandboxCreateAttempts = 0;
while (!sandbox && numSandboxCreateAttempts < 3) {
sandbox = await createSandbox(numSandboxCreateAttempts);
if (!sandbox) {
numSandboxCreateAttempts++;
}
}
if (!sandbox) {
throw new Error("Failed to create sandbox after 3 attempts");
}
const { githubInstallationToken } = getGitHubTokensFromConfig(config);
// Clone repository
@ -130,14 +164,16 @@ export async function getSandboxWithErrorHandling(
});
// Get codebase tree
const codebaseTree = await getCodebaseTree(sandbox.id);
const codebaseTree = await getCodebaseTree(sandbox.id, targetRepository);
const codebaseTreeToReturn =
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE ? null : codebaseTree;
logger.info("Sandbox created successfully", {
sandboxId: sandbox.id,
});
return {
sandbox,
codebaseTree,
codebaseTree: codebaseTreeToReturn,
dependenciesInstalled: false,
};
}

View file

@ -1,36 +1,78 @@
import { getCurrentTaskInput } from "@langchain/langgraph";
import { GraphState } from "@open-swe/shared/open-swe/types";
import { GraphState, TargetRepository } from "@open-swe/shared/open-swe/types";
import { daytonaClient } from "./sandbox.js";
import { createLogger, LogLevel } from "./logger.js";
import path from "node:path";
import { SANDBOX_ROOT_DIR, TIMEOUT_SEC } from "@open-swe/shared/constants";
import { getSandboxErrorFields } from "./sandbox-error-fields.js";
const logger = createLogger(LogLevel.INFO, "Tree");
export async function getCodebaseTree(sandboxSessionId_?: string) {
const command = `git ls-files | tree --fromfile -L 3`;
const state = getCurrentTaskInput<GraphState>();
const sandboxSessionId = sandboxSessionId_ || state.sandboxSessionId;
if (!sandboxSessionId) {
logger.error("Failed to generate tree: No sandbox session ID provided");
throw new Error("Failed generate tree: No sandbox session ID provided");
}
const sandbox = await daytonaClient().get(sandboxSessionId);
const repoDir = path.join(SANDBOX_ROOT_DIR, state.targetRepository.repo);
const response = await sandbox.process.executeCommand(
command,
repoDir,
undefined,
TIMEOUT_SEC,
);
export const FAILED_TO_GENERATE_TREE_MESSAGE =
"Failed to generate tree. Please try again.";
if (response.exitCode !== 0) {
export async function getCodebaseTree(
sandboxSessionId_?: string,
targetRepository_?: TargetRepository,
): Promise<string> {
try {
const command = `git ls-files | tree --fromfile -L 3`;
let sandboxSessionId = sandboxSessionId_;
let targetRepository = targetRepository_;
// If sandbox session ID is not provided, try to get it from the current state.
if (!sandboxSessionId || !targetRepository) {
try {
const state = getCurrentTaskInput<GraphState>();
// Prefer the provided sandbox session ID and target repository. Fallback to state if defined.
sandboxSessionId = sandboxSessionId ?? state.sandboxSessionId;
targetRepository = targetRepository ?? state.targetRepository;
} catch {
// not executed in a LangGraph instance. continue.
}
}
if (!sandboxSessionId) {
logger.error("Failed to generate tree: No sandbox session ID provided");
throw new Error("Failed generate tree: No sandbox session ID provided");
}
if (!targetRepository) {
logger.error("Failed to generate tree: No target repository provided");
throw new Error("Failed generate tree: No target repository provided");
}
const sandbox = await daytonaClient().get(sandboxSessionId);
const repoDir = path.join(SANDBOX_ROOT_DIR, targetRepository.repo);
const response = await sandbox.process.executeCommand(
command,
repoDir,
undefined,
TIMEOUT_SEC,
);
if (response.exitCode !== 0) {
logger.error("Failed to generate tree", {
exitCode: response.exitCode,
result: response.result ?? response.artifacts?.stdout,
});
throw new Error(
`Failed to generate tree: ${response.result ?? response.artifacts?.stdout}`,
);
}
return response.result;
} catch (e) {
const errorFields = getSandboxErrorFields(e);
logger.error("Failed to generate tree", {
error: response.result,
error_result: response,
...(errorFields ? { errorFields } : {}),
...(e instanceof Error
? {
name: e.name,
message: e.message,
stack: e.stack,
}
: {}),
});
throw new Error(`Failed to generate tree: ${response.result}`);
return FAILED_TO_GENERATE_TREE_MESSAGE;
}
return response.result;
}