fix: bug with checking out the branch during followups (#679)

* fix: bug with checking out the branch during followups

* cr

* cr

* cr

* cr
This commit is contained in:
Brace Sproul 2025-08-06 09:41:11 -07:00 • committed by GitHub
parent 0591a907c7
commit df182b676b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 139 additions and 118 deletions

View file

@ -25,7 +25,7 @@ export async function submitFeedback({
plannerFeedback: string; plannerFeedback: string;
plannerThreadId: string; plannerThreadId: string;
setLogs: (updater: (prev: string[]) => string[]) => void; // eslint-disable-line no-unused-vars setLogs: (updater: (prev: string[]) => string[]) => void; // eslint-disable-line no-unused-vars
setPlannerFeedback: () => void; // eslint-disable-line no-unused-vars setPlannerFeedback: () => void;
setStreamingPhase: (phase: "streaming" | "awaitingFeedback" | "done") => void; // eslint-disable-line no-unused-vars setStreamingPhase: (phase: "streaming" | "awaitingFeedback" | "done") => void; // eslint-disable-line no-unused-vars
}) { }) {
try { try {

View file

@ -74,7 +74,7 @@ Before starting, ensure you have the following installed:
GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback" GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback"
# GitHub App details (will be filled after creating GitHub App) # GitHub App details (will be filled after creating GitHub App)
GITHUB_APP_NAME="open-swe-dev" GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces
GITHUB_APP_ID="" GITHUB_APP_ID=""
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...add your private key here... ...add your private key here...
@ -105,7 +105,7 @@ Before starting, ensure you have the following installed:
FIRECRAWL_API_KEY="" # For URL content extraction FIRECRAWL_API_KEY="" # For URL content extraction
# GitHub App settings (same as web app) # GitHub App settings (same as web app)
GITHUB_APP_NAME="open-swe-dev" GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces
GITHUB_APP_ID="" GITHUB_APP_ID=""
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...add your private key here... ...add your private key here...

View file

@ -25,9 +25,7 @@ FIRECRAWL_API_KEY=""
# ------------------Github App Secrets----------------- # ------------------Github App Secrets-----------------
# Used for setting the git user name & email for commits. GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces
# Can modify to whatever string you want.
GITHUB_APP_NAME="open-swe-dev"
GITHUB_APP_ID="" GITHUB_APP_ID=""
# App secret key. Should be multi-line. # App secret key. Should be multi-line.
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
@ -54,5 +52,6 @@ SECRETS_ENCRYPTION_KEY=""
# See the documentation for how to set this up: docs.langchain.com/labs/swe/setup/ci#skip-ci-until-last-commit # See the documentation for how to set this up: docs.langchain.com/labs/swe/setup/ci#skip-ci-until-last-commit
SKIP_CI_UNTIL_LAST_COMMIT="true" SKIP_CI_UNTIL_LAST_COMMIT="true"
OPEN_SWE_LOCAL_MODE=true # For the CLI to work, you need to set these variables.
OPEN_SWE_LOCAL_PROJECT_PATH="" # OPEN_SWE_LOCAL_MODE=false
# OPEN_SWE_LOCAL_PROJECT_PATH=""

View file

@ -24,7 +24,7 @@
"postinstall": "turbo build" "postinstall": "turbo build"
}, },
"dependencies": { "dependencies": {
"@daytonaio/sdk": "^0.25.4", "@daytonaio/sdk": "^0.25.5",
"@langchain/anthropic": "^0.3.26", "@langchain/anthropic": "^0.3.26",
"@langchain/community": "^0.3.47", "@langchain/community": "^0.3.47",
"@langchain/core": "^0.3.65", "@langchain/core": "^0.3.65",

View file

@ -286,10 +286,13 @@ export async function initializeSandbox(
stateBranchName: branchName, stateBranchName: branchName,
}); });
}, },
{ retries: 3, delay: 0 }, { retries: 0, delay: 0 },
); );
if (cloneRepoRes instanceof Error) { if (
cloneRepoRes instanceof Error &&
!cloneRepoRes.message.includes("repository already exists")
) {
emitStepEvent( emitStepEvent(
baseCloneRepoAction, baseCloneRepoAction,
"error", "error",

View file

@ -1,6 +1,7 @@
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { createLogger, LogLevel } from "../logger.js"; import { createLogger, LogLevel } from "../logger.js";
import { import {
GitHubBranch,
GitHubIssue, GitHubIssue,
GitHubIssueComment, GitHubIssueComment,
GitHubPullRequest, GitHubPullRequest,
@ -555,3 +556,35 @@ export async function updateIssueComment({
numRetries, numRetries,
); );
} }
export async function getBranch({
owner,
repo,
branchName,
githubInstallationToken,
}: {
owner: string;
repo: string;
branchName: string;
githubInstallationToken: string;
}): Promise<GitHubBranch | null> {
return withGitHubRetry(
async (token: string) => {
const octokit = new Octokit({
auth: token,
});
const { data: branch } = await octokit.repos.getBranch({
owner,
repo,
branch: branchName,
});
return branch;
},
githubInstallationToken,
"Failed to get branch",
undefined,
1,
);
}

View file

@ -15,7 +15,7 @@ import {
getActiveTask, getActiveTask,
getPullRequestNumberFromActiveTask, getPullRequestNumberFromActiveTask,
} from "@open-swe/shared/open-swe/tasks"; } from "@open-swe/shared/open-swe/tasks";
import { createPullRequest } from "./api.js"; import { createPullRequest, getBranch } from "./api.js";
import { addTaskPlanToIssue } from "./issue-task.js"; import { addTaskPlanToIssue } from "./issue-task.js";
import { DEFAULT_EXCLUDED_PATTERNS } from "./constants.js"; import { DEFAULT_EXCLUDED_PATTERNS } from "./constants.js";
import { escapeRegExp } from "../string-utils.js"; import { escapeRegExp } from "../string-utils.js";
@ -523,11 +523,32 @@ async function performClone(
baseCommit: targetRepository.baseCommit, baseCommit: targetRepository.baseCommit,
}); });
if (!branchName && !targetRepository.baseCommit) {
throw new Error(
"Can not create new branch or checkout existing branch without branch name",
);
}
const branchExists = branchName
? !!(await getBranch({
owner: targetRepository.owner,
repo: targetRepository.repo,
branchName,
githubInstallationToken,
}))
: false;
if (branchExists) {
logger.info("Branch already exists on remote. Cloning existing branch.", {
branch: branchName,
});
}
await sandbox.git.clone( await sandbox.git.clone(
cloneUrl, cloneUrl,
absoluteRepoDir, absoluteRepoDir,
targetRepository.branch, branchExists ? branchName : targetRepository.branch,
targetRepository.baseCommit, branchExists ? undefined : targetRepository.baseCommit,
"git", "git",
githubInstallationToken, githubInstallationToken,
); );
@ -543,45 +564,51 @@ async function performClone(
} }
if (!branchName) { if (!branchName) {
throw new Error( throw new Error("Branch name is required");
"Can not create new branch or checkout existing branch without branch name", }
);
if (branchExists) {
return branchName;
} }
try { try {
logger.info("Creating branch", {
branch: branchName,
});
await sandbox.git.createBranch(absoluteRepoDir, branchName); await sandbox.git.createBranch(absoluteRepoDir, branchName);
logger.info("Created branch", { logger.info("Created branch", {
branch: branchName, branch: branchName,
}); });
} catch (error) {
// push an empty commit so that the branch exists in the remote logger.error("Failed to create branch, checking out branch", {
await sandbox.git.push(absoluteRepoDir, "git", githubInstallationToken);
logger.info("Pushed empty commit to remote", {
branch: branchName,
});
return branchName;
} catch {
logger.info("Failed to create branch, checking out branch", {
branch: branchName, branch: branchName,
error:
error instanceof Error
? { name: error.name, message: error.message }
: String(error),
}); });
} }
await sandbox.git.checkoutBranch(absoluteRepoDir, branchName); try {
logger.info("Checked out branch", { // push an empty commit so that the branch exists in the remote
branch: branchName, logger.info("Pushing empty commit to remote", {
}); branch: branchName,
});
const setUpstreamBranchRes = await sandbox.process.executeCommand( await sandbox.git.push(absoluteRepoDir, "git", githubInstallationToken);
`git branch --set-upstream-to=origin/${branchName}`,
absoluteRepoDir, logger.info("Pushed empty commit to remote", {
); branch: branchName,
if (setUpstreamBranchRes.exitCode !== 0) { });
logger.error("Failed to set upstream branch", { } catch (error) {
setUpstreamBranchRes, logger.error("Failed to push an empty commit to branch", {
branch: branchName,
error:
error instanceof Error
? { name: error.name, message: error.message }
: String(error),
}); });
} else {
logger.info("Set upstream branch");
} }
return branchName; return branchName;

View file

@ -14,3 +14,6 @@ export type GitHubPullRequestUpdate =
export type GitHubPullRequestList = export type GitHubPullRequestList =
RestEndpointMethodTypes["pulls"]["list"]["response"]["data"]; RestEndpointMethodTypes["pulls"]["list"]["response"]["data"];
export type GitHubBranch =
RestEndpointMethodTypes["repos"]["getBranch"]["response"]["data"];

View file

@ -7,9 +7,7 @@ GITHUB_APP_CLIENT_SECRET=""
# your GitHub app settings. # your GitHub app settings.
GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback" GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback"
# Used for setting the git user name & email for commits. GITHUB_APP_NAME="open-swe-dev" # this must match the name of your GitHub app, excluding spaces
# Can modify to whatever string you want.
GITHUB_APP_NAME="open-swe-dev"
GITHUB_APP_ID="" GITHUB_APP_ID=""
# App secret key. Should be multi-line. # App secret key. Should be multi-line.
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----

View file

@ -208,45 +208,6 @@ export function ConfigManager() {
updateConfig(DEFAULT_CONFIG_KEY, config.label, value); updateConfig(DEFAULT_CONFIG_KEY, config.label, value);
}} }}
/> />
<div className="flex items-center justify-between">
{(() => {
const currentValue =
configs[DEFAULT_CONFIG_KEY]?.[config.label];
const defaultValue = defaultConfig.find(
(c) => c.label === config.label,
)?.default;
const isModified =
currentValue !== undefined &&
currentValue !== defaultValue;
return (
isModified && (
<Button
size="sm"
variant="outline"
onClick={() => {
// Remove the config value to revert to default
// This effectively "unsets" the user's custom value
const existingConfig =
getConfig(DEFAULT_CONFIG_KEY) || {};
const {
[config.label]: _,
...remainingConfig
} = existingConfig;
updateConfig(
DEFAULT_CONFIG_KEY,
"",
remainingConfig,
);
}}
>
Reset to Default
</Button>
)
);
})()}
</div>
</div> </div>
</div> </div>
), ),

View file

@ -1,10 +1,16 @@
import { MessageContent } from "@langchain/core/messages"; import { MessageContent } from "@langchain/core/messages";
export function getMessageContentString(content: MessageContent): string { export function getMessageContentString(content: MessageContent): string {
if (typeof content === "string") return content; try {
if (typeof content === "string") return content;
return content return content
.filter((c): c is { type: "text"; text: string } => c.type === "text") .filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text) .map((c) => c.text)
.join(" "); .join(" ");
} catch (error) {
// eslint-disable-next-line no-console
console.error("Failed to get message content string", error);
return "";
}
} }

View file

@ -1,12 +1,13 @@
export const MODEL_OPTIONS = [ export const MODEL_OPTIONS = [
{ // TODO: Test these then re-enable
label: "Claude Sonnet 4 (Extended Thinking)", // {
value: "anthropic:extended-thinking:claude-sonnet-4-0", // label: "Claude Sonnet 4 (Extended Thinking)",
}, // value: "anthropic:extended-thinking:claude-sonnet-4-0",
{ // },
label: "Claude Opus 4 (Extended Thinking)", // {
value: "anthropic:extended-thinking:claude-opus-4-0", // label: "Claude Opus 4 (Extended Thinking)",
}, // value: "anthropic:extended-thinking:claude-opus-4-0",
// },
{ {
label: "Claude Sonnet 4", label: "Claude Sonnet 4",
value: "anthropic:claude-sonnet-4-0", value: "anthropic:claude-sonnet-4-0",

View file

@ -1282,33 +1282,32 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@daytonaio/api-client@npm:0.25.4": "@daytonaio/api-client@npm:0.25.5":
version: 0.25.4 version: 0.25.5
resolution: "@daytonaio/api-client@npm:0.25.4" resolution: "@daytonaio/api-client@npm:0.25.5"
dependencies: dependencies:
axios: ^1.6.1 axios: ^1.6.1
checksum: c3ff09da2af437620bada5c6161e52104ec32a2ef0f3a890d4da5458015e78e9de7ee28954e423477dc5fd7a8a07f58e5fd1fc919d9f382e86b6ad2da8b421c6 checksum: 208fd54526f9f3b40ef8ca137e6f6d68ac4cef0e63b51cda1cb7a91ffa23dc3875a49bfc4eda271f0f1cb86ba21e33234b68b5a51591882cc13508ccba29f8d2
languageName: node languageName: node
linkType: hard linkType: hard
"@daytonaio/sdk@npm:^0.25.4": "@daytonaio/sdk@npm:^0.25.5":
version: 0.25.4 version: 0.25.5
resolution: "@daytonaio/sdk@npm:0.25.4" resolution: "@daytonaio/sdk@npm:0.25.5"
dependencies: dependencies:
"@aws-sdk/client-s3": ^3.787.0 "@aws-sdk/client-s3": ^3.787.0
"@aws-sdk/lib-storage": ^3.798.0 "@aws-sdk/lib-storage": ^3.798.0
"@daytonaio/api-client": 0.25.4 "@daytonaio/api-client": 0.25.5
"@iarna/toml": ^2.2.5 "@iarna/toml": ^2.2.5
axios: ^1.6.1 axios: ^1.6.1
dotenv: ^17.0.1 dotenv: ^17.0.1
expand-tilde: ^2.0.2 expand-tilde: ^2.0.2
fast-glob: ^3.3.0 fast-glob: ^3.3.0
form-data: ^4.0.0 form-data: ^4.0.4
isomorphic-ws: 5.0.0
pathe: ^2.0.3 pathe: ^2.0.3
shell-quote: ^1.8.2 shell-quote: ^1.8.2
tar: ^6.2.0 tar: ^6.2.0
checksum: d3d6e35c8c27a69c4cd8e241b9e1e4ac2171136718dc0ec21bf20e63e0d027f29a37ab16909025f14f891428055c9493382d72fe368d8db391249f59b4f00e21 checksum: 692edc134c56bb83037870a750b81792b5592fdc5cf13d9b2d6ecfd522866d433bfb9e8943a3d4caf169b1d69969048079b0e876354e83035840fed4173b0c37
languageName: node languageName: node
linkType: hard linkType: hard
@ -4093,7 +4092,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@open-swe/agent@workspace:apps/open-swe" resolution: "@open-swe/agent@workspace:apps/open-swe"
dependencies: dependencies:
"@daytonaio/sdk": ^0.25.4 "@daytonaio/sdk": ^0.25.5
"@eslint/eslintrc": ^3.1.0 "@eslint/eslintrc": ^3.1.0
"@eslint/js": ^9.19.0 "@eslint/js": ^9.19.0
"@jest/globals": ^29.7.0 "@jest/globals": ^29.7.0
@ -11348,7 +11347,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"form-data@npm:^4.0.0, form-data@npm:^4.0.4": "form-data@npm:^4.0.4":
version: 4.0.4 version: 4.0.4
resolution: "form-data@npm:4.0.4" resolution: "form-data@npm:4.0.4"
dependencies: dependencies:
@ -13103,15 +13102,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"isomorphic-ws@npm:5.0.0":
version: 5.0.0
resolution: "isomorphic-ws@npm:5.0.0"
peerDependencies:
ws: "*"
checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398
languageName: node
linkType: hard
"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0":
version: 3.2.2 version: 3.2.2
resolution: "istanbul-lib-coverage@npm:3.2.2" resolution: "istanbul-lib-coverage@npm:3.2.2"