mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-02 01:33:15 +00:00
feat: Support skipping Vercel's CI (#588)
* fix: filtering gh bot comments * cr * cr * test empty commit * [skip ci] * [skip deploy] * feat: Support skipping Vercel's CI * push empty commit at the end * cr * testing [skip ci] * testing [skip deploy] * testing [skip ci] * testing [skip ci] * testing [skip ci] * fix command * cr * cr * cr * cr
This commit is contained in:
parent
fb705d367a
commit
2f2e543f8f
6 changed files with 162 additions and 2 deletions
|
|
@ -27,7 +27,8 @@
|
||||||
"setup/intro",
|
"setup/intro",
|
||||||
"setup/development",
|
"setup/development",
|
||||||
"setup/authentication",
|
"setup/authentication",
|
||||||
"setup/monorepo"
|
"setup/monorepo",
|
||||||
|
"setup/ci"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
BIN
apps/docs/images/vercel_ignored_build_script_screenshot.png
Normal file
BIN
apps/docs/images/vercel_ignored_build_script_screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
69
apps/docs/setup/ci.mdx
Normal file
69
apps/docs/setup/ci.mdx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
title: "CI Configuration"
|
||||||
|
description: "How to configure your CI pipeline for Open SWE"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skip CI until last commit
|
||||||
|
|
||||||
|
Open SWE will push a commit after every change to the repository. This will cause your GitHub CI workflows to run for every commit, which is unnecessary.
|
||||||
|
|
||||||
|
To prevent this, Open SWE supports adding an environment variable which will append `[skip ci]` to the commit message. This can be used to make GitHub skip CI for that commit.
|
||||||
|
|
||||||
|
Below are instructions showing how to use this to skip creating Vercel CI preview deployments on every commit:
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Set the environment variable">
|
||||||
|
Set the environment variable `SKIP_CI_UNTIL_LAST_COMMIT` to `true` in Open SWE's environment variables:
|
||||||
|
|
||||||
|
`apps/open-swe/.env`
|
||||||
|
```bash
|
||||||
|
SKIP_CI_UNTIL_LAST_COMMIT="true"
|
||||||
|
```
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Add custom 'Ignored Build Step' in Vercel">
|
||||||
|
Add a custom 'Ignored Build Step' in Vercel to skip CI when commit messages contain the string `[skip ci]`.
|
||||||
|
|
||||||
|
1. Navigate to your Vercel project dashboard
|
||||||
|
2. Go to **Settings** → **Git** tab
|
||||||
|
3. Scroll to **Ignored Build Step** section
|
||||||
|
4. Select **Custom** and add the following script:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```bash
|
||||||
|
git log -1 --pretty=oneline --abbrev-commit | grep -w "\[skip ci\]" && exit 0 || exit 1
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="warning">
|
||||||
|
**Do not test this command locally** as it will close your terminal. Use the testing method below instead.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Test the configuration (optional)">
|
||||||
|
To verify your setup works without closing your terminal, use this safe testing command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Safe local testing - won't close your terminal
|
||||||
|
if git log -1 --pretty=oneline --abbrev-commit | grep -w "\[skip ci\]"; then
|
||||||
|
echo "Found [skip ci] - Vercel would skip this build"
|
||||||
|
else
|
||||||
|
echo "No [skip ci] found - Vercel would proceed with build"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Test with both types of commits:
|
||||||
|
- Commits with `[skip ci]` should show "Vercel would skip this build"
|
||||||
|
- Commits without `[skip ci]` should show "Vercel would proceed with build"
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
1. **During task execution**: Open SWE includes `[skip ci]` in commit messages
|
||||||
|
2. **Vercel behavior**: Skips creating preview deployments for these commits (script exits with code 1)
|
||||||
|
3. **Task completion**: Open SWE pushes a final commit without `[skip ci]` to trigger deployment (script exits with code 0)
|
||||||
|
4. **Result**: Only one preview deployment per Open SWE task
|
||||||
|
|
@ -50,3 +50,6 @@ OPEN_SWE_APP_URL="http://localhost:3000"
|
||||||
# Should be the same value as the one used in the web app, so that secrets
|
# Should be the same value as the one used in the web app, so that secrets
|
||||||
# encrypted in the web app can be decrypted in the agent.
|
# encrypted in the web app can be decrypted in the agent.
|
||||||
SECRETS_ENCRYPTION_KEY=""
|
SECRETS_ENCRYPTION_KEY=""
|
||||||
|
# Whether or not to append the string "[skip ci]" to the commit message.
|
||||||
|
# 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"
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import {
|
import {
|
||||||
checkoutBranchAndCommit,
|
checkoutBranchAndCommit,
|
||||||
getChangedFilesStatus,
|
getChangedFilesStatus,
|
||||||
|
pushEmptyCommit,
|
||||||
} from "../../../utils/github/git.js";
|
} from "../../../utils/github/git.js";
|
||||||
import {
|
import {
|
||||||
createPullRequest,
|
createPullRequest,
|
||||||
|
|
@ -167,6 +168,12 @@ export async function openPullRequest(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.SKIP_CI_UNTIL_LAST_COMMIT === "true") {
|
||||||
|
await pushEmptyCommit(state.targetRepository, sandbox, {
|
||||||
|
githubInstallationToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { title, body } = toolCall.args as z.infer<typeof openPrTool.schema>;
|
const { title, body } = toolCall.args as z.infer<typeof openPrTool.schema>;
|
||||||
|
|
||||||
const prForTask = getPullRequestNumberFromActiveTask(
|
const prForTask = getPullRequestNumberFromActiveTask(
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,16 @@ export async function stashAndClearChanges(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function constructCommitMessage(): string {
|
||||||
|
const baseCommitMessage = "Apply patch";
|
||||||
|
const skipCiString = "[skip ci]";
|
||||||
|
const vercelSkipCi = process.env.SKIP_CI_UNTIL_LAST_COMMIT === "true";
|
||||||
|
if (vercelSkipCi) {
|
||||||
|
return `${baseCommitMessage} ${skipCiString}`;
|
||||||
|
}
|
||||||
|
return baseCommitMessage;
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkoutBranchAndCommit(
|
export async function checkoutBranchAndCommit(
|
||||||
config: GraphConfig,
|
config: GraphConfig,
|
||||||
targetRepository: TargetRepository,
|
targetRepository: TargetRepository,
|
||||||
|
|
@ -203,7 +213,12 @@ export async function checkoutBranchAndCommit(
|
||||||
}
|
}
|
||||||
const userName = `${botAppName}[bot]`;
|
const userName = `${botAppName}[bot]`;
|
||||||
const userEmail = `${botAppName}@users.noreply.github.com`;
|
const userEmail = `${botAppName}@users.noreply.github.com`;
|
||||||
await sandbox.git.commit(absoluteRepoDir, "Apply patch", userName, userEmail);
|
await sandbox.git.commit(
|
||||||
|
absoluteRepoDir,
|
||||||
|
constructCommitMessage(),
|
||||||
|
userName,
|
||||||
|
userEmail,
|
||||||
|
);
|
||||||
|
|
||||||
// Push the changes using the git API so it handles authentication for us.
|
// Push the changes using the git API so it handles authentication for us.
|
||||||
const pushRes = await withRetry(
|
const pushRes = await withRetry(
|
||||||
|
|
@ -301,6 +316,71 @@ export async function checkoutBranchAndCommit(
|
||||||
return { branchName, updatedTaskPlan };
|
return { branchName, updatedTaskPlan };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function pushEmptyCommit(
|
||||||
|
targetRepository: TargetRepository,
|
||||||
|
sandbox: Sandbox,
|
||||||
|
options: {
|
||||||
|
githubInstallationToken: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const botAppName = process.env.GITHUB_APP_NAME;
|
||||||
|
if (!botAppName) {
|
||||||
|
logger.error("GITHUB_APP_NAME environment variable is not set.");
|
||||||
|
throw new Error("GITHUB_APP_NAME environment variable is not set.");
|
||||||
|
}
|
||||||
|
const userName = `${botAppName}[bot]`;
|
||||||
|
const userEmail = `${botAppName}@users.noreply.github.com`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const absoluteRepoDir = getRepoAbsolutePath(targetRepository);
|
||||||
|
const setGitConfigRes = await sandbox.process.executeCommand(
|
||||||
|
`git config user.name "${userName}" && git config user.email "${userEmail}"`,
|
||||||
|
absoluteRepoDir,
|
||||||
|
undefined,
|
||||||
|
TIMEOUT_SEC,
|
||||||
|
);
|
||||||
|
if (setGitConfigRes.exitCode !== 0) {
|
||||||
|
logger.error(`Failed to set git config`, {
|
||||||
|
exitCode: setGitConfigRes.exitCode,
|
||||||
|
result: setGitConfigRes.result,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyCommitRes = await sandbox.process.executeCommand(
|
||||||
|
"git commit --allow-empty -m 'Empty commit to trigger CI'",
|
||||||
|
absoluteRepoDir,
|
||||||
|
undefined,
|
||||||
|
TIMEOUT_SEC,
|
||||||
|
);
|
||||||
|
if (emptyCommitRes.exitCode !== 0) {
|
||||||
|
logger.error(`Failed to push empty commit`, {
|
||||||
|
exitCode: emptyCommitRes.exitCode,
|
||||||
|
result: emptyCommitRes.result,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sandbox.git.push(
|
||||||
|
absoluteRepoDir,
|
||||||
|
"git",
|
||||||
|
options.githubInstallationToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info("Successfully pushed empty commit");
|
||||||
|
} catch (e) {
|
||||||
|
const errorFields = getSandboxErrorFields(e);
|
||||||
|
logger.error(`Failed to push empty commit`, {
|
||||||
|
...(errorFields && { errorFields }),
|
||||||
|
...(e instanceof Error && {
|
||||||
|
name: e.name,
|
||||||
|
message: e.message,
|
||||||
|
stack: e.stack,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function pullLatestChanges(
|
export async function pullLatestChanges(
|
||||||
absoluteRepoDir: string,
|
absoluteRepoDir: string,
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue