mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 12:43:16 +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/development",
|
||||
"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
|
||||
# encrypted in the web app can be decrypted in the agent.
|
||||
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 {
|
||||
checkoutBranchAndCommit,
|
||||
getChangedFilesStatus,
|
||||
pushEmptyCommit,
|
||||
} from "../../../utils/github/git.js";
|
||||
import {
|
||||
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 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(
|
||||
config: GraphConfig,
|
||||
targetRepository: TargetRepository,
|
||||
|
|
@ -203,7 +213,12 @@ export async function checkoutBranchAndCommit(
|
|||
}
|
||||
const userName = `${botAppName}[bot]`;
|
||||
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.
|
||||
const pushRes = await withRetry(
|
||||
|
|
@ -301,6 +316,71 @@ export async function checkoutBranchAndCommit(
|
|||
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(
|
||||
absoluteRepoDir: string,
|
||||
sandbox: Sandbox,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue