Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/code/worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@ When a watched PR falls behind its base branch, the worker catches the branch up

This applies only to the agent's own PRs (the same watch list as review polling). Each base/head SHA pair is a durable event in `.devintern-code/queue.db`; new commits on the PR branch open a fresh event, so an exhausted attempt is retried after the next push. Failures retry up to `WEBHOOK_MAX_RETRIES` (default 3), including across worker restarts. Before acting, the worker requires the PR head SHA to remain unchanged for `WORKER_BASE_SYNC_QUIET_SECONDS` (default 30) and then re-fetches both SHAs. Recent or concurrent pushes defer the run without consuming an attempt; if a run defers several times in a row, the event is given up until the head or base moves again. GitHub's PR API can report an outdated `base.sha` for a while, so the resolver always merges the actual fetched tip of the base branch rather than trusting that field. Each resolve run is bounded by `WORKER_RESOLVE_TIMEOUT_SECONDS` (default 1800; `0` disables) — a hung resolver subprocess is killed and counted as a failed attempt, and runs left `in_progress` by a crashed or killed worker are marked failed at the next startup. The same merge logic is available manually for any PR via `devintern resolve-conflicts <pr-url>`.

## CI failures on the agent's PRs

The worker also watches GitHub Actions check runs and commit statuses on the agent's own PRs and fixes them without human intervention. When a check run completes with a `failure` conclusion (or a classic commit status reports failure) on the PR's current head SHA, the worker fetches the failing jobs' logs, reduces them to the error-relevant excerpt, and runs the agent through the same pipeline review feedback uses: worktree prep, sandboxed spawn, commit, push. The push restarts CI, closing the loop from "agent opened PR" to "PR green".

Guardrails keep a red build from turning into churn:

- Only terminal failures trigger a fix — pending and in-progress runs are ignored (the agent's own pushes constantly restart CI).
- A given failure is fixed at most once: attempts dedupe per head SHA plus check run id, and the mapping survives worker restarts.
- Consecutive failed autofix attempts are capped per PR (`CI_FIX_MAX_ATTEMPTS`, default 3). On exhaustion the worker posts a comment escalating to a human and stops retrying until someone pushes to the branch; CI going green resets the counter.
- Fork PRs are skipped quietly (Actions rarely runs on forks, and the agent cannot push there).
- When job logs cannot be downloaded (token scope, expired logs), the watcher falls back to the check run's annotations before giving up and letting the agent reproduce the failure locally.

The same fix runs execute under the workspace's configured sandbox (`AGENT_SANDBOX`) exactly like review-feedback runs, and each attempt is recorded in the dashboard with the `ci_fix` origin. Downloading Actions job logs needs sufficient token scope (a PAT with `repo`, or a GitHub App with `actions:read`).

## Mention the bot on any PR

The worker also reacts to mentions on pull requests it did not create. When a teammate writes a comment like `@devintern address the review feedback` on any PR in the repository, the worker picks it up on the next poll and handles it through the same pipeline. Detection is a repository-wide sweep of new comments (two requests per interval, regardless of how many PRs are open), so mentions work without any webhook setup.
Expand Down
8 changes: 7 additions & 1 deletion packages/code/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -825,12 +825,15 @@ if (process.argv[2] === "init") {
let noPush = false;
let noReply = false;
let verbose = false;
let ciFeedbackPath: string | undefined;

for (let i = 0; i < args.length; i++) {
if (args[i] === "--no-push") {
noPush = true;
} else if (args[i] === "--no-reply") {
noReply = true;
} else if (args[i] === "--ci-feedback") {
ciFeedbackPath = args[++i];
} else if (args[i] === "-v" || args[i] === "--verbose") {
verbose = true;
} else if (args[i] === "--help" || args[i] === "-h") {
Expand All @@ -846,6 +849,8 @@ if (process.argv[2] === "init") {
console.log("Options:");
console.log(" --no-push Don't push changes after fixing");
console.log(" --no-reply Don't post a reply comment on the PR");
console.log(" --ci-feedback <path> Fix CI failures from a feedback JSON file");
console.log(" (written by the worker's CI failure watcher)");
console.log(" -v, --verbose Enable verbose logging");
console.log(" -h, --help Display this help message");
console.log("");
Expand All @@ -869,7 +874,7 @@ if (process.argv[2] === "init") {
// Import and run address-review
const { addressReview } = await import("./lib/address-review");
try {
await addressReview(prUrl, { noPush, noReply, verbose });
await addressReview(prUrl, { noPush, noReply, verbose, ciFeedbackPath });
} catch (error) {
// Close any run record addressReview opened before it failed (no-op
// when none is active — addressReview also ends runs it completes).
Expand Down Expand Up @@ -1155,6 +1160,7 @@ Subcommands:
dashboard Serve the local observability dashboard (run history and stats)
webhook serve Start the advanced repo-local direct-webhook server
address-review Address review feedback on an existing pull request
(--ci-feedback <file> fixes CI failures instead)
resolve-conflicts Merge a PR's base branch into it, resolving conflicts
login [method] Sign in (github | google | x | email; prompts if omitted)
logout Clear local auth session
Expand Down
Loading
Loading