From afac1eab8b5c2aedef490899210156c79ebe8afe Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 May 2025 17:41:38 -0700 Subject: [PATCH] fix: Handle apply patch errors better (#27) --- apps/open-swe/scripts/run-e2e.ts | 4 +-- apps/open-swe/scripts/run-from-plan.ts | 17 ++++++--- apps/open-swe/src/nodes/generate-message.ts | 1 + apps/open-swe/src/utils/read-write.ts | 40 +++++++++++++++++++++ 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/apps/open-swe/scripts/run-e2e.ts b/apps/open-swe/scripts/run-e2e.ts index cd386341..8015b903 100644 --- a/apps/open-swe/scripts/run-e2e.ts +++ b/apps/open-swe/scripts/run-e2e.ts @@ -41,7 +41,7 @@ Once you're done, ensure you've documented the development process in the readme }, config: { configurable, - recursion_limit: 200, + recursion_limit: 400, }, ifNotExists: "create", streamSubgraphs: true, @@ -85,7 +85,7 @@ async function resumeGraph(threadId: string) { }, config: { configurable, - recursion_limit: 200, + recursion_limit: 400, }, streamSubgraphs: true, streamMode: "updates", diff --git a/apps/open-swe/scripts/run-from-plan.ts b/apps/open-swe/scripts/run-from-plan.ts index 69e14abf..063611ee 100644 --- a/apps/open-swe/scripts/run-from-plan.ts +++ b/apps/open-swe/scripts/run-from-plan.ts @@ -19,8 +19,15 @@ async function runFromPlan() { messages: [ { role: "user", - content: - "This repo contains the react/next.js code for my persona/portfolio site. It currently has static values set for the number of stars on the repositories I highlight. I want this to be accurate, but I do NOT want it to make requests to GitHub every time a user visits. Instead, please implement a solution which will run once a day, fetch the number of stars from a list of repos, then write them to vercel's KV store. Finally, update the UI to make a request to the KV store when the user visits my page and render the accurate star counts.", + content: `The project is structured as a monorepo, with some apps located inside the /apps directory. In this directory, there is an /auth directory. This directory only contains the scaffolding for a new app in the monorepo, but is not yet implemented. Please take the following plan/task description and implement it in the /auth directory: +This monorepo is for an AI coding agent. The app runs and edits the code in the cloud in a sandboxed environment. Right now, we require users to generate a GitHub PAT, which we store in a .env file and can use to authenticate with GitHub. This is not idea, and instead we want to have a github oauth app which users can authenticate with. +Please implement a new auth server inside the /auth directory which can do this. +You will not have any access to secrets, so you will not be able to run the server to test it. +I want the server to be able to authenticate users with GitHub, such that we will be able to take the following actions: +1. clone repositories they give us access to +2. checkout existing and create new branches on the repositories they give us access to +3. make pull requests and push changes to the repositories they give us access to +Once you're done, ensure you've documented the development process in the readme of this new app.`, }, { id: "toolu_01RNEjViZFBXgCSNMwqKvkVL", @@ -116,8 +123,8 @@ async function runFromPlan() { "thread_id" | "assistant_id" > = { target_repository: { - owner: "bracesproul", - repo: "personal-site", + owner: "langchain-ai", + repo: "open-swe", }, }; @@ -144,7 +151,7 @@ async function runFromPlan() { }, config: { configurable, - recursion_limit: 200, + recursion_limit: 400, }, ifNotExists: "create", streamSubgraphs: true, diff --git a/apps/open-swe/src/nodes/generate-message.ts b/apps/open-swe/src/nodes/generate-message.ts index 0bae0746..abf703e0 100644 --- a/apps/open-swe/src/nodes/generate-message.ts +++ b/apps/open-swe/src/nodes/generate-message.ts @@ -44,6 +44,7 @@ You MUST adhere to the following criteria when executing the task: - Each summary message will also include a short description of the task it completed, how it did so, and every change it made to the codebase during this task. This section will be titled 'Repository modifications summary'. - All changes are automatically committed, so you should not worry about creating backups, or committing changes. - Use \`apply_patch\` to edit files. This tool accepts diffs and file paths. It will then apply the given diff to the file. +- You should NOT try to create empty files with \`apply_patch\`. If you need to create a file, use the \`shell\` tool, and pass \`touch \` to create the file. - When using the \`shell\` tool, always take advantage of the \`workdir\` parameter to run commands inside the repo directory. You should not try to generate a command with \`cd \` as passing that path to \`workdir\` is much more efficient. - Do not try to install dependencies, or run a server, compile the code, etc., unless you are explicitly asked to. - If completing the user's task requires writing or modifying files: diff --git a/apps/open-swe/src/utils/read-write.ts b/apps/open-swe/src/utils/read-write.ts index 1728aa5a..00e7edf3 100644 --- a/apps/open-swe/src/utils/read-write.ts +++ b/apps/open-swe/src/utils/read-write.ts @@ -6,6 +6,33 @@ import { traceable } from "langsmith/traceable"; const logger = createLogger(LogLevel.INFO, "ReadWriteUtil"); +async function handleCreateFile( + sandbox: Sandbox, + filePath: string, + args?: { + workDir?: string; + }, +) { + try { + const touchCommand = `touch "${filePath}"`; + const touchOutput = await sandbox.commands.run(touchCommand, { + cwd: args?.workDir, + }); + return touchOutput; + } catch (e) { + const errorFields = getSandboxErrorFields(e); + if (errorFields) { + return errorFields; + } + return { + exitCode: 1, + error: e instanceof Error ? e.message : String(e), + stdout: "", + stderr: "", + }; + } +} + async function readFileFunc( sandbox: Sandbox, filePath: string, @@ -42,6 +69,19 @@ async function readFileFunc( output: readOutput.stdout, }; } catch (e: any) { + if (e instanceof Error && e.message.includes("No such file or directory")) { + const createOutput = await handleCreateFile(sandbox, filePath, args); + if (createOutput.exitCode !== 0) { + return { + success: false, + output: `FAILED TO EXECUTE READ COMMAND for sandbox '${filePath}'. Error: ${(e as Error).message || String(e)}`, + }; + } else { + // If the file was created successfully, try reading it again. + return readFile(sandbox, filePath, args); + } + } + logger.error( `Exception while trying to read file '${filePath}' from sandbox via cat:`, {