Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ checkpoint, and status-only commits are intentionally omitted.

### Added

- Added repair-only PR intake that scans an author's open pull requests for actionable failures and creates durable PR-repair jobs. Thanks @Jhacarreiro.
- Added automatic issue-build lifecycle comments and dashboard cards with issue titles, queued/planning/building/completed/blocked history, live worker links, Actions runs, and generated PR drill-down.
- Show issue and pull request titles alongside target numbers on active dashboard worker cards and worker detail links.
- Added comprehensive documentation for steerable repair automation, covering issue-to-PR and PR-repair intake, GitCrawl Actions consumption, deduplication, opt-out labels, GitHub App token boundaries, durable Codex thread resumption, CrabFleet steering, worker budgets, completion gates, dashboards, and failure recovery.
Expand Down Expand Up @@ -64,6 +65,7 @@ checkpoint, and status-only commits are intentionally omitted.

### Changed

- Removed the unsupported ephemeral-session flag from repair Codex subprocess invocations. Thanks @Jhacarreiro.
- Enabled automatic implementation plus bounded durable-report backfill for eligible open issues; general viable implementation remains limited to public sibling repositories, while separately gated strict-bug and vision-fit lanes can backfill `openclaw/openclaw`. Codex discovers viable implementation and validation strategy, while deterministic security, opt-out, source-state, quota, report-revision receipt, queued-job, and PR/cluster deduplication gates remain.
- Increased quiet scheduled review capacity from 48 to 64 workers, switched scheduled backfill to three-item shards to reduce setup and tail-idle overhead, and made seven-day review freshness an explicit scheduler priority.
- Doubled the global Codex worker budget to 128 with proportional reserves, added job-level dashboard error and recovery rates, and moved the bounded failed-review retry backstop to hourly.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
"crabbox:hydrate": "crabbox actions hydrate",
"crabbox:run": "crabbox run",
"crabbox:stop": "crabbox stop",
"crabbox:warmup": "crabbox warmup"
"crabbox:warmup": "crabbox warmup",
"repair:pr-intake": "node dist/repair/pr-repair-intake.js"
},
"devDependencies": {
"@types/node": "^25.9.2",
Expand Down
4 changes: 3 additions & 1 deletion src/repair/action-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ export function actionWorkKind(frontmatter: LooseRecord): ActionWorkKind {
}
if (
frontmatter.job_intent === "automerge_pr" ||
frontmatter.job_intent === "pr_repair" ||
frontmatter.source === "pr_automerge" ||
String(frontmatter.cluster_id ?? "").startsWith("automerge-")
String(frontmatter.cluster_id ?? "").startsWith("automerge-") ||
String(frontmatter.cluster_id ?? "").startsWith("repair-pr-")
) {
return "pr_repair";
}
Expand Down
6 changes: 0 additions & 6 deletions src/repair/execute-fix-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2015,7 +2015,6 @@ function editValidatePrepareMerge({
...codexConfigArgs(),
"--output-last-message",
summaryPath,
"--ephemeral",
"--json",
"-",
],
Expand Down Expand Up @@ -2388,7 +2387,6 @@ function runCodexBaseReconcile({
...codexConfigArgs(),
"--output-last-message",
summaryPath,
"--ephemeral",
"--json",
"-",
],
Expand Down Expand Up @@ -2469,7 +2467,6 @@ function runCodexWritePreflight() {
...codexConfigArgs(),
"--output-last-message",
summaryPath,
"--ephemeral",
"--json",
"--skip-git-repo-check",
"-",
Expand Down Expand Up @@ -2820,7 +2817,6 @@ function runCodexReview({
schemaPath,
"--output-last-message",
outputPath,
"--ephemeral",
"--json",
"-",
],
Expand Down Expand Up @@ -2933,7 +2929,6 @@ function runCodexReviewFix({ fixArtifact, targetDir, mode, review, attempt }: Lo
...codexConfigArgs(),
"--output-last-message",
path.join(workRoot, `${mode}-codex-review-fix-${attempt}.md`),
"--ephemeral",
"--json",
"-",
],
Expand Down Expand Up @@ -3012,7 +3007,6 @@ function runCodexValidationFix({
...codexConfigArgs(),
"--output-last-message",
path.join(workRoot, `${mode}-codex-validation-fix-${attempt}.md`),
"--ephemeral",
"--json",
"-",
],
Expand Down
35 changes: 34 additions & 1 deletion src/repair/execute-fix-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from "node:path";

import type { JsonValue, LooseRecord } from "./json-types.js";
import { GITHUB_PR_TITLE_MAX_LENGTH } from "./pr-title.js";
import { slug } from "./text-utils.js";

const REPAIR_STRATEGIES = new Set([
"repair_contributor_branch",
Expand Down Expand Up @@ -187,7 +188,11 @@ function isTrustedIssueImplementation({ job, fixArtifact }: LooseRecord): boolea
function isTrustedAdoptedBranchRepair({ job, fixArtifact }: LooseRecord): boolean {
const frontmatter = job.frontmatter ?? {};
if (fixArtifact.repair_strategy !== "repair_contributor_branch") return false;
if (frontmatter.source !== "pr_autofix" && frontmatter.source !== "pr_automerge") return false;
const trustedSource =
frontmatter.source === "pr_autofix" ||
frontmatter.source === "pr_automerge" ||
isTrustedPrRepairIntake(frontmatter, fixArtifact);
if (!trustedSource) return false;
if (frontmatter.allow_fix_pr !== true) return false;
if (!Array.isArray(frontmatter.allowed_actions) || !frontmatter.allowed_actions.includes("fix")) {
return false;
Expand All @@ -204,6 +209,34 @@ function isTrustedAdoptedBranchRepair({ job, fixArtifact }: LooseRecord): boolea
});
}

function isTrustedPrRepairIntake(frontmatter: LooseRecord, fixArtifact: LooseRecord): boolean {
if (frontmatter.source !== "pr-repair-intake" || frontmatter.job_intent !== "pr_repair") {
return false;
}
const repo = String(frontmatter.repo ?? "")
.trim()
.toLowerCase();
if (!/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/.test(repo)) return false;
if (!Array.isArray(fixArtifact.source_prs) || fixArtifact.source_prs.length !== 1) return false;
const sourcePr = String(fixArtifact.source_prs[0] ?? "").toLowerCase();
const sourceMatch = sourcePr.match(
new RegExp(
`^https://github\\.com/${repo.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/pull/([1-9]\\d*)$`,
),
);
if (!sourceMatch) return false;
const sourceRef = `#${sourceMatch[1]}`;
for (const key of ["canonical", "candidates", "cluster_refs"]) {
const refs = frontmatter[key];
if (!Array.isArray(refs) || refs.length !== 1 || refs[0] !== sourceRef) return false;
}
const expectedClusterId = slug(`repair-pr-${repo.replace("/", "-")}-${sourceMatch[1]}`);
return (
frontmatter.cluster_id === expectedClusterId &&
frontmatter.target_branch === `clawsweeper/${expectedClusterId}`
);
}

function readSiblingJson(resultPath: string, name: string): LooseRecord | null {
const file = path.join(path.dirname(resultPath), name);
if (!fs.existsSync(file)) return null;
Expand Down
3 changes: 2 additions & 1 deletion src/repair/git-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ function shouldPreserveStateOnlyFile(
rel: string,
sourceHasPath: (path: string) => boolean,
): boolean {
if (path === "jobs") return /^[^/]+\/inbox\/(?:automerge|issue|self-heal)-.+\.md$/.test(rel);
if (path === "jobs")
return /^[^/]+\/inbox\/(?:automerge|issue|self-heal|repair-pr)-.+\.md$/.test(rel);
const publishedPath = joinedPublishPath(path, rel);
if (!publishedPath.startsWith("records/")) return false;
const counterpart = recordCounterpartPath(publishedPath);
Expand Down
2 changes: 2 additions & 0 deletions src/repair/job-intent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { JsonValue, LooseRecord } from "./json-types.js";
export const REPAIR_JOB_INTENTS = [
"repair_cluster",
"automerge_pr",
"pr_repair",
"clawsweeper_self_rebase",
"implement_issue",
"commit_finding",
Expand Down Expand Up @@ -48,6 +49,7 @@ export function repairJobIntentForFrontmatter(frontmatter: LooseRecord): RepairJ

export function workerLaneForRepairJobIntent(intent: RepairJobIntent): WorkerLane {
if (intent === "automerge_pr") return "automerge_repair";
if (intent === "pr_repair") return "automerge_repair";
if (intent === "clawsweeper_self_rebase") return "automerge_repair";
if (intent === "implement_issue") return "issue_implementation";
return "repair";
Expand Down
Loading
Loading