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 { Command } from "@langchain/langgraph";
import { truncateOutput } from "../../../utils/truncate-outputs.js"; import { truncateOutput } from "../../../utils/truncate-outputs.js";
import { getSandboxWithErrorHandling } from "../../../utils/sandbox.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 { getRepoAbsolutePath } from "@open-swe/shared/git";
import { createInstallDependenciesTool } from "../../../tools/install-dependencies.js"; import { createInstallDependenciesTool } from "../../../tools/install-dependencies.js";
import { createSearchTool } from "../../../tools/search.js"; import { createSearchTool } from "../../../tools/search.js";
@ -180,6 +183,11 @@ export async function takeAction(
]); ]);
const codebaseTree = await getCodebaseTree(); 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 // Prioritize wereDependenciesInstalled over dependenciesInstalled
const dependenciesInstalledUpdate = const dependenciesInstalledUpdate =
@ -193,7 +201,7 @@ export async function takeAction(
messages: toolCallResults, messages: toolCallResults,
internalMessages: toolCallResults, internalMessages: toolCallResults,
...(branchName && { branchName }), ...(branchName && { branchName }),
codebaseTree, codebaseTree: codebaseTreeToReturn,
sandboxSessionId: sandbox.id, sandboxSessionId: sandbox.id,
...(dependenciesInstalledUpdate !== null && { ...(dependenciesInstalledUpdate !== null && {
dependenciesInstalled: dependenciesInstalledUpdate, dependenciesInstalled: dependenciesInstalledUpdate,

View file

@ -14,7 +14,10 @@ import {
configureGitUserInRepo, configureGitUserInRepo,
pullLatestChanges, pullLatestChanges,
} from "../../utils/github/git.js"; } 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 { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
import { import {
CustomNodeEvent, CustomNodeEvent,
@ -171,7 +174,15 @@ export async function initializeSandbox(
emitStepEvent(baseGenerateCodebaseTreeAction, "pending"); emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
try { try {
const codebaseTree = await getCodebaseTree(existingSandbox.id); 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 { return {
sandboxSessionId: existingSandbox.id, sandboxSessionId: existingSandbox.id,
@ -183,9 +194,14 @@ export async function initializeSandbox(
emitStepEvent( emitStepEvent(
baseGenerateCodebaseTreeAction, baseGenerateCodebaseTreeAction,
"error", "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 { } catch {
emitStepEvent( emitStepEvent(

View file

@ -5,7 +5,7 @@ import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../constants.js";
import { getGitHubTokensFromConfig } from "./github-tokens.js"; import { getGitHubTokensFromConfig } from "./github-tokens.js";
import { cloneRepo, configureGitUserInRepo } from "./github/git.js"; import { cloneRepo, configureGitUserInRepo } from "./github/git.js";
import { getRepoAbsolutePath } from "@open-swe/shared/git"; 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"); 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( export async function getSandboxWithErrorHandling(
sandboxSessionId: string | undefined, sandboxSessionId: string | undefined,
targetRepository: TargetRepository, targetRepository: TargetRepository,
@ -112,7 +134,19 @@ export async function getSandboxWithErrorHandling(
error, 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); const { githubInstallationToken } = getGitHubTokensFromConfig(config);
// Clone repository // Clone repository
@ -130,14 +164,16 @@ export async function getSandboxWithErrorHandling(
}); });
// Get codebase tree // 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", { logger.info("Sandbox created successfully", {
sandboxId: sandbox.id, sandboxId: sandbox.id,
}); });
return { return {
sandbox, sandbox,
codebaseTree, codebaseTree: codebaseTreeToReturn,
dependenciesInstalled: false, dependenciesInstalled: false,
}; };
} }

View file

@ -1,36 +1,78 @@
import { getCurrentTaskInput } from "@langchain/langgraph"; 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 { daytonaClient } from "./sandbox.js";
import { createLogger, LogLevel } from "./logger.js"; import { createLogger, LogLevel } from "./logger.js";
import path from "node:path"; import path from "node:path";
import { SANDBOX_ROOT_DIR, TIMEOUT_SEC } from "@open-swe/shared/constants"; import { SANDBOX_ROOT_DIR, TIMEOUT_SEC } from "@open-swe/shared/constants";
import { getSandboxErrorFields } from "./sandbox-error-fields.js";
const logger = createLogger(LogLevel.INFO, "Tree"); const logger = createLogger(LogLevel.INFO, "Tree");
export async function getCodebaseTree(sandboxSessionId_?: string) { export const FAILED_TO_GENERATE_TREE_MESSAGE =
const command = `git ls-files | tree --fromfile -L 3`; "Failed to generate tree. Please try again.";
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,
);
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", { logger.error("Failed to generate tree", {
error: response.result, ...(errorFields ? { errorFields } : {}),
error_result: response, ...(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;
} }