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;
plannerThreadId: string;
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
}) {
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 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_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...add your private key here...
@ -105,7 +105,7 @@ Before starting, ensure you have the following installed:
FIRECRAWL_API_KEY="" # For URL content extraction
# 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_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...add your private key here...

View file

@ -25,9 +25,7 @@ FIRECRAWL_API_KEY=""
# ------------------Github App Secrets-----------------
# Used for setting the git user name & email for commits.
# Can modify to whatever string you want.
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=""
# App secret key. Should be multi-line.
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
SKIP_CI_UNTIL_LAST_COMMIT="true"
OPEN_SWE_LOCAL_MODE=true
OPEN_SWE_LOCAL_PROJECT_PATH=""
# For the CLI to work, you need to set these variables.
# OPEN_SWE_LOCAL_MODE=false
# OPEN_SWE_LOCAL_PROJECT_PATH=""

View file

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

View file

@ -286,10 +286,13 @@ export async function initializeSandbox(
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(
baseCloneRepoAction,
"error",

View file

@ -1,6 +1,7 @@
import { Octokit } from "@octokit/rest";
import { createLogger, LogLevel } from "../logger.js";
import {
GitHubBranch,
GitHubIssue,
GitHubIssueComment,
GitHubPullRequest,
@ -555,3 +556,35 @@ export async function updateIssueComment({
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,
getPullRequestNumberFromActiveTask,
} 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 { DEFAULT_EXCLUDED_PATTERNS } from "./constants.js";
import { escapeRegExp } from "../string-utils.js";
@ -523,11 +523,32 @@ async function performClone(
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(
cloneUrl,
absoluteRepoDir,
targetRepository.branch,
targetRepository.baseCommit,
branchExists ? branchName : targetRepository.branch,
branchExists ? undefined : targetRepository.baseCommit,
"git",
githubInstallationToken,
);
@ -543,45 +564,51 @@ async function performClone(
}
if (!branchName) {
throw new Error(
"Can not create new branch or checkout existing branch without branch name",
);
throw new Error("Branch name is required");
}
if (branchExists) {
return branchName;
}
try {
logger.info("Creating branch", {
branch: branchName,
});
await sandbox.git.createBranch(absoluteRepoDir, branchName);
logger.info("Created branch", {
branch: branchName,
});
// push an empty commit so that the branch exists in the remote
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", {
} catch (error) {
logger.error("Failed to create branch, checking out branch", {
branch: branchName,
error:
error instanceof Error
? { name: error.name, message: error.message }
: String(error),
});
}
await sandbox.git.checkoutBranch(absoluteRepoDir, branchName);
logger.info("Checked out branch", {
branch: branchName,
});
const setUpstreamBranchRes = await sandbox.process.executeCommand(
`git branch --set-upstream-to=origin/${branchName}`,
absoluteRepoDir,
);
if (setUpstreamBranchRes.exitCode !== 0) {
logger.error("Failed to set upstream branch", {
setUpstreamBranchRes,
try {
// push an empty commit so that the branch exists in the remote
logger.info("Pushing empty commit to remote", {
branch: branchName,
});
await sandbox.git.push(absoluteRepoDir, "git", githubInstallationToken);
logger.info("Pushed empty commit to remote", {
branch: branchName,
});
} catch (error) {
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;

View file

@ -14,3 +14,6 @@ export type GitHubPullRequestUpdate =
export type GitHubPullRequestList =
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.
GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback"
# Used for setting the git user name & email for commits.
# Can modify to whatever string you want.
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=""
# App secret key. Should be multi-line.
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----

View file

@ -208,45 +208,6 @@ export function ConfigManager() {
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>
),

View file

@ -1,10 +1,16 @@
import { MessageContent } from "@langchain/core/messages";
export function getMessageContentString(content: MessageContent): string {
if (typeof content === "string") return content;
try {
if (typeof content === "string") return content;
return content
.filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text)
.join(" ");
return content
.filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text)
.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 = [
{
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",
},
// TODO: Test these then re-enable
// {
// 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 Sonnet 4",
value: "anthropic:claude-sonnet-4-0",

View file

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