@humanbased/crosscheck 1.3.0-beta.93 → 1.3.0-beta.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/auto-fix-branch.test.d.ts +2 -0
- package/dist/__tests__/auto-fix-branch.test.d.ts.map +1 -0
- package/dist/__tests__/auto-fix-branch.test.js +143 -0
- package/dist/__tests__/auto-fix-branch.test.js.map +1 -0
- package/dist/__tests__/superseded-fix-pr.test.d.ts +2 -0
- package/dist/__tests__/superseded-fix-pr.test.d.ts.map +1 -0
- package/dist/__tests__/superseded-fix-pr.test.js +225 -0
- package/dist/__tests__/superseded-fix-pr.test.js.map +1 -0
- package/dist/__tests__/webhook.test.js +60 -0
- package/dist/__tests__/webhook.test.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +24 -0
- package/dist/commands/watch.js.map +1 -1
- package/dist/github/superseded-fix-pr.d.ts +19 -0
- package/dist/github/superseded-fix-pr.d.ts.map +1 -0
- package/dist/github/superseded-fix-pr.js +97 -0
- package/dist/github/superseded-fix-pr.js.map +1 -0
- package/dist/github/webhook.d.ts +3 -1
- package/dist/github/webhook.d.ts.map +1 -1
- package/dist/github/webhook.js +8 -1
- package/dist/github/webhook.js.map +1 -1
- package/dist/lib/auto-fix-branch.d.ts +35 -0
- package/dist/lib/auto-fix-branch.d.ts.map +1 -0
- package/dist/lib/auto-fix-branch.js +88 -0
- package/dist/lib/auto-fix-branch.js.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +215 -22
- package/dist/lib/runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// The separate-fix-PR path is the fallback taken when a fix cannot be pushed onto the
|
|
2
|
+
// PR's own branch. Two things about it were broken in practice, and both are visible in
|
|
3
|
+
// real logs: 43 fix PRs opened, 19 `base: invalid` API failures, 12 push rejections.
|
|
4
|
+
//
|
|
5
|
+
// 1. It opened the PR against the source PR's head branch — which is missing in exactly
|
|
6
|
+
// the case that sends us down this path (PR merged, branch auto-deleted). The
|
|
7
|
+
// fallback failed for its own primary trigger, after having already pushed a branch,
|
|
8
|
+
// leaving that branch orphaned.
|
|
9
|
+
// 2. It pushed with a plain `git push`, so a fix branch left behind by an earlier round
|
|
10
|
+
// of the same PR made the push non-fast-forward and the fix was lost.
|
|
11
|
+
import { autoFixBranchName, isCrosscheckAutoFixPR } from '../github/superseded-fix-pr.js';
|
|
12
|
+
export function planAutoFixDelivery(baseBranchExists, baseBranch) {
|
|
13
|
+
return baseBranchExists
|
|
14
|
+
? { kind: 'pull_request', base: baseBranch }
|
|
15
|
+
: { kind: 'comment', reason: 'base_branch_gone' };
|
|
16
|
+
}
|
|
17
|
+
// `--force-with-lease=<ref>:<oid>` states the remote value we believe we are replacing.
|
|
18
|
+
// With an empty oid it means "expect this ref not to exist", so one form covers both
|
|
19
|
+
// creating the branch and replacing a superseded one — and a branch someone else moved
|
|
20
|
+
// in between fails the lease instead of being overwritten.
|
|
21
|
+
//
|
|
22
|
+
// Force is appropriate here only because the branch is crosscheck's own artifact for
|
|
23
|
+
// one PR and one finding: the newer fix is built on the newer PR head and supersedes
|
|
24
|
+
// whatever the previous round pushed. It is never used on a branch a human owns.
|
|
25
|
+
export function forceWithLeaseArgs(branch, remoteOid) {
|
|
26
|
+
return [
|
|
27
|
+
'push',
|
|
28
|
+
`--force-with-lease=refs/heads/${branch}:${remoteOid ?? ''}`,
|
|
29
|
+
'origin',
|
|
30
|
+
`HEAD:${branch}`,
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
// `git ls-remote --heads origin <branch>` prints "<oid>\t<ref>" per match, and nothing
|
|
34
|
+
// at all when the branch does not exist.
|
|
35
|
+
export function parseLsRemoteOid(output) {
|
|
36
|
+
const line = output.split('\n').map(l => l.trim()).find(Boolean);
|
|
37
|
+
if (!line)
|
|
38
|
+
return null;
|
|
39
|
+
const oid = line.split(/\s+/)[0];
|
|
40
|
+
return /^[0-9a-f]{40}$/i.test(oid) ? oid : null;
|
|
41
|
+
}
|
|
42
|
+
export function isLeaseRejection(message) {
|
|
43
|
+
return /stale info|force-with-lease|fetch first|non-fast-forward/i.test(message);
|
|
44
|
+
}
|
|
45
|
+
// Whether the fix branch that already exists on the remote is crosscheck's own artifact,
|
|
46
|
+
// and so safe to replace.
|
|
47
|
+
//
|
|
48
|
+
// Anything the pusher controls is not evidence: a commit message trailer, an author or
|
|
49
|
+
// committer name, and the branch name itself are all free-form strings that whoever
|
|
50
|
+
// creates `fix/cr-<n>-review-issues` can set to whatever they like — so a human branch
|
|
51
|
+
// carrying a `Crosscheck-Reviewer:` trailer would read as crosscheck's and be
|
|
52
|
+
// force-overwritten. Ownership is instead taken from GitHub's own record: a pull request
|
|
53
|
+
// **opened by the identity crosscheck authenticates as**, from this exact branch, whose
|
|
54
|
+
// body carries the auto-fix marker. Another actor cannot produce that record without
|
|
55
|
+
// crosscheck's credentials.
|
|
56
|
+
//
|
|
57
|
+
// The residual case is an install whose token belongs to a human who also works by hand:
|
|
58
|
+
// there, "crosscheck" and "that person" are one account and no check can separate them.
|
|
59
|
+
//
|
|
60
|
+
// Closed PRs count — the branch is still crosscheck's artifact once it has opened a PR
|
|
61
|
+
// from it. A branch with no such PR (an orphan left by a round that pushed before PR
|
|
62
|
+
// creation failed) is deliberately not replaced: the fix is delivered as a diff instead,
|
|
63
|
+
// which loses the follow-up PR but never someone else's commits.
|
|
64
|
+
export function assessFixBranchOwnership(input) {
|
|
65
|
+
if (!input.crosscheckLogin)
|
|
66
|
+
return { owned: false, reason: 'identity_unknown' };
|
|
67
|
+
const login = input.crosscheckLogin.toLowerCase();
|
|
68
|
+
const fixBranch = autoFixBranchName(input.sourcePrNumber);
|
|
69
|
+
const ours = input.candidates.find(candidate => candidate.head?.ref === fixBranch
|
|
70
|
+
&& candidate.user?.login?.toLowerCase() === login
|
|
71
|
+
&& isCrosscheckAutoFixPR(candidate.body, input.sourcePrNumber));
|
|
72
|
+
return ours ? { owned: true, fixPrNumber: ours.number } : { owned: false, reason: 'no_crosscheck_fix_pr' };
|
|
73
|
+
}
|
|
74
|
+
// `pulls.create` rejecting the base branch, which is how a source PR that merges between
|
|
75
|
+
// the pre-flight check and the create call reports itself: GitHub deleted the head branch
|
|
76
|
+
// in that window, so the base we validated is gone by the time we ask for the PR.
|
|
77
|
+
export function isInvalidBaseError(err) {
|
|
78
|
+
const failure = err;
|
|
79
|
+
if (failure?.status !== 422)
|
|
80
|
+
return false;
|
|
81
|
+
const errors = failure.response?.data?.errors ?? failure.errors ?? [];
|
|
82
|
+
if (errors.some(entry => entry?.field === 'base' && entry?.code === 'invalid'))
|
|
83
|
+
return true;
|
|
84
|
+
// Octokit's RequestError embeds the response body in its message; a shape we don't
|
|
85
|
+
// recognise field-by-field is still readable there.
|
|
86
|
+
return /"field"\s*:\s*"base"/.test(failure.message ?? '');
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=auto-fix-branch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-fix-branch.js","sourceRoot":"","sources":["../../src/lib/auto-fix-branch.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAoB,MAAM,qBAAqB,CAAA;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAqB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/runner.js
CHANGED
|
@@ -11,6 +11,7 @@ import { runFixStep, runCodexFixStep } from '../reviewers/fix.js';
|
|
|
11
11
|
import { runConflictResolveStep, findConflictedFiles } from '../reviewers/conflict-resolve.js';
|
|
12
12
|
import { parseVerdict, prependVerdictToComment, NULL_VERDICT_WARNING, applySeverityGate, SEVERITY_GATE_NOTE } from '../lib/verdict.js';
|
|
13
13
|
import { createGithubClient, postReviewComment, getLastCrossCheckCommentId, getLastCrossCheckReviewComment } from '../github/client.js';
|
|
14
|
+
import { autoFixBranchName, autoFixPRIntro, sourcePRHasMerged } from '../github/superseded-fix-pr.js';
|
|
14
15
|
import { verifyReviewedSha, isVerifiedReviewedSha, reviewedShaRejection } from '../github/reviewed-sha.js';
|
|
15
16
|
import { resolveLinearAuth, withWorker } from '../linear/identity.js';
|
|
16
17
|
import { notifyLinear } from '../linear/notify.js';
|
|
@@ -23,6 +24,7 @@ import { resolveClaudeModel, resolveCodexModel } from '../lib/review-models.js';
|
|
|
23
24
|
import { resolveReviewStrategy, escalate, clampToLevels } from './review-strategy.js';
|
|
24
25
|
import { CLAUDE_EFFORT_LEVELS, CODEX_EFFORT_LEVELS } from '../config/schema.js';
|
|
25
26
|
import { buildStepIdentityFields } from '../lib/event-fields.js';
|
|
27
|
+
import { planAutoFixDelivery, forceWithLeaseArgs, parseLsRemoteOid, isLeaseRejection, assessFixBranchOwnership, isInvalidBaseError } from '../lib/auto-fix-branch.js';
|
|
26
28
|
import { prOpenToVerdictMs } from '../lib/adoption.js';
|
|
27
29
|
import { buildAttributionFooter, buildFixAppliedCommentBody, buildFixFailedCommentBody, buildConflictResolvedCommentBody, buildRetriedReviewBanner } from '../lib/comment-bodies.js';
|
|
28
30
|
import { linearWritePossible, loadWorkflow, loadHarnessSection, evaluateWhen } from '../lib/workflow.js';
|
|
@@ -1413,39 +1415,230 @@ export async function runWorkflow(ctx) {
|
|
|
1413
1415
|
results[step.name] = { applied_count: appliedCount, tokens_used: fixTokensUsed, vendor: activeVendor };
|
|
1414
1416
|
}
|
|
1415
1417
|
else {
|
|
1416
|
-
// Fallback: the fix could not land on the PR branch. Push the same commit to
|
|
1417
|
-
//
|
|
1418
|
-
const fixBranch =
|
|
1419
|
-
|
|
1420
|
-
cwd: tmpDir,
|
|
1421
|
-
env: { ...process.env, GITHUB_TOKEN: token, GH_TOKEN: token },
|
|
1422
|
-
});
|
|
1418
|
+
// Fallback: the fix could not land on the PR branch. Push the same commit to a
|
|
1419
|
+
// dedicated branch and open a follow-up PR targeting the original branch.
|
|
1420
|
+
const fixBranch = autoFixBranchName(prNumber);
|
|
1421
|
+
const gitEnv = { ...process.env, GITHUB_TOKEN: token, GH_TOKEN: token };
|
|
1423
1422
|
const octokit = createGithubClient(token);
|
|
1423
|
+
// The fallback commit exists only in this clone until something pushes it, so
|
|
1424
|
+
// every path that ends without delivering it as a PR has to put HEAD back on the
|
|
1425
|
+
// PR head. Otherwise a later recheck reviews that commit and stamps its verdict
|
|
1426
|
+
// either with a sha the repository cannot resolve — which verifyReviewedSha
|
|
1427
|
+
// refuses to post (#290) — or with one no open PR carries.
|
|
1428
|
+
const restorePRHeadInClone = () => {
|
|
1429
|
+
execSync('git reset --hard HEAD~1', { cwd: tmpDir, stdio: 'pipe' });
|
|
1430
|
+
};
|
|
1431
|
+
// Deliver the fix as a diff on the original PR. Used wherever opening the
|
|
1432
|
+
// follow-up PR would either create a stale artifact or overwrite a branch that
|
|
1433
|
+
// is not ours: the work still reaches a human, just not as a branch.
|
|
1434
|
+
const deliverFixAsComment = async (outcome) => {
|
|
1435
|
+
fileLog({ level: 'warn', event: outcome.event, repo: `${owner}/${repoName}`, pr: prNumber, branch: outcome.branch, reason: outcome.reason });
|
|
1436
|
+
log(chalk.yellow(`⚠ ${outcome.notice}`));
|
|
1437
|
+
let patch = '';
|
|
1438
|
+
try {
|
|
1439
|
+
patch = execSync('git diff HEAD~1', { cwd: tmpDir, encoding: 'utf8' });
|
|
1440
|
+
}
|
|
1441
|
+
catch { /* fall through to the empty check */ }
|
|
1442
|
+
if (patch) {
|
|
1443
|
+
await octokit.rest.issues.createComment({
|
|
1444
|
+
owner, repo: repoName, issue_number: prNumber,
|
|
1445
|
+
body: [
|
|
1446
|
+
'### Suggested fixes (crosscheck auto-fix)',
|
|
1447
|
+
'',
|
|
1448
|
+
outcome.explanation,
|
|
1449
|
+
'',
|
|
1450
|
+
'```diff',
|
|
1451
|
+
patch.slice(0, 16000),
|
|
1452
|
+
'```',
|
|
1453
|
+
'',
|
|
1454
|
+
fixAttributionFooter(),
|
|
1455
|
+
].join('\n'),
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
restorePRHeadInClone();
|
|
1459
|
+
onPhaseChange('fixed ✓', { fixCount: appliedCount, phase: 'fixed', fixTokens: fixTokensUsed });
|
|
1460
|
+
fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'comment', tokens_used: fixTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - fixStepStart, ...triggerField });
|
|
1461
|
+
results[step.name] = { applied_count: appliedCount, tokens_used: fixTokensUsed, vendor: activeVendor };
|
|
1462
|
+
};
|
|
1463
|
+
// Ask whether the intended base still exists BEFORE pushing anything. The most
|
|
1464
|
+
// common reason the PR-branch push failed is that the PR merged and GitHub
|
|
1465
|
+
// deleted its branch — and `pulls.create` rejects a missing base with
|
|
1466
|
+
// `field: base, code: invalid`. That used to throw after the branch was already
|
|
1467
|
+
// pushed, losing the fix and orphaning the branch (19 times in logged runs).
|
|
1468
|
+
let baseBranchExists = true;
|
|
1469
|
+
try {
|
|
1470
|
+
await octokit.rest.repos.getBranch({ owner, repo: repoName, branch: pr.head.ref });
|
|
1471
|
+
}
|
|
1472
|
+
catch (err) {
|
|
1473
|
+
if (err?.status === 404)
|
|
1474
|
+
baseBranchExists = false;
|
|
1475
|
+
else
|
|
1476
|
+
throw err;
|
|
1477
|
+
}
|
|
1478
|
+
const delivery = planAutoFixDelivery(baseBranchExists, pr.head.ref);
|
|
1479
|
+
if (delivery.kind === 'comment') {
|
|
1480
|
+
// A PR cut from this snapshot would be stale the moment it opened — it is
|
|
1481
|
+
// exactly the artifact the superseded-auto-fix cleanup exists to close. Post
|
|
1482
|
+
// the fix as a diff instead, so the work is still delivered and reviewable.
|
|
1483
|
+
await deliverFixAsComment({
|
|
1484
|
+
event: 'fix_pr_skipped',
|
|
1485
|
+
reason: delivery.reason,
|
|
1486
|
+
branch: pr.head.ref,
|
|
1487
|
+
notice: `${pr.head.ref} no longer exists — posting the fix as a diff instead of opening a stale follow-up PR`,
|
|
1488
|
+
explanation: `\`${pr.head.ref}\` no longer exists, so this PR has merged and there is no branch to land on. The fix is below rather than in a follow-up PR — a PR cut from the pre-merge state would reintroduce whatever the merged version changed.`,
|
|
1489
|
+
});
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
// A fix branch left behind by an earlier round of this PR made a plain push
|
|
1493
|
+
// non-fast-forward and lost the fix (12 times in logged runs). Replace it under
|
|
1494
|
+
// a lease: this round's fix is built on the newer PR head and supersedes it,
|
|
1495
|
+
// and the lease still refuses if anything other than us moved the branch.
|
|
1496
|
+
let remoteOid = null;
|
|
1497
|
+
try {
|
|
1498
|
+
remoteOid = parseLsRemoteOid(execFileSync('git', ['ls-remote', '--heads', 'origin', fixBranch], { cwd: tmpDir, env: gitEnv, encoding: 'utf8' }));
|
|
1499
|
+
}
|
|
1500
|
+
catch { /* treat an unreadable remote as "branch absent"; the lease catches a wrong guess */ }
|
|
1501
|
+
// `--force-with-lease` only protects against changes made *after* this lookup —
|
|
1502
|
+
// it happily accepts a branch a human just created or moved as the lease value
|
|
1503
|
+
// and overwrites it. So establish that the branch is crosscheck's own artifact
|
|
1504
|
+
// before replacing it, from GitHub's record of who opened the PR from it rather
|
|
1505
|
+
// than from anything the pusher can write into a commit.
|
|
1506
|
+
if (remoteOid !== null) {
|
|
1507
|
+
let crosscheckLogin = null;
|
|
1508
|
+
let candidates = [];
|
|
1509
|
+
try {
|
|
1510
|
+
const [{ data: viewer }, { data: branchPrs }] = await Promise.all([
|
|
1511
|
+
octokit.rest.users.getAuthenticated(),
|
|
1512
|
+
octokit.rest.pulls.list({ owner, repo: repoName, state: 'all', head: `${owner}:${fixBranch}` }),
|
|
1513
|
+
]);
|
|
1514
|
+
crosscheckLogin = viewer.login;
|
|
1515
|
+
candidates = branchPrs;
|
|
1516
|
+
}
|
|
1517
|
+
catch { /* unreadable identity or PR list — treated as not ours, never overwrite */ }
|
|
1518
|
+
const ownership = assessFixBranchOwnership({ sourcePrNumber: prNumber, crosscheckLogin, candidates });
|
|
1519
|
+
if (!ownership.owned) {
|
|
1520
|
+
await deliverFixAsComment({
|
|
1521
|
+
event: 'fix_branch_push_skipped',
|
|
1522
|
+
reason: ownership.reason,
|
|
1523
|
+
branch: fixBranch,
|
|
1524
|
+
notice: `${fixBranch} exists but crosscheck has no PR of its own from it — leaving it in place and posting the fix as a diff instead`,
|
|
1525
|
+
explanation: `\`${fixBranch}\` already exists and crosscheck cannot show it opened a pull request from it, so the branch was left untouched rather than force-replaced. The fix is posted here instead.`,
|
|
1526
|
+
});
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
// From #296: the merged-PR handler sweeps for auto-fix PRs once, so a PR opened
|
|
1531
|
+
// after that sweep is never seen by it and stays open and mergeable against a
|
|
1532
|
+
// tree that no longer exists. The base-exists check above already covers a merge
|
|
1533
|
+
// that deleted the branch; this catches a merge in a repo that keeps its
|
|
1534
|
+
// branches. It sits immediately before the push — every lookup above widens the
|
|
1535
|
+
// window it has to close, and nothing has been written to the remote yet, so a
|
|
1536
|
+
// skip here cannot orphan a branch.
|
|
1537
|
+
if (await sourcePRHasMerged(octokit, owner, repoName, prNumber)) {
|
|
1538
|
+
restorePRHeadInClone();
|
|
1539
|
+
skipFix('source_pr_merged');
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
try {
|
|
1543
|
+
execFileSync('git', forceWithLeaseArgs(fixBranch, remoteOid), { cwd: tmpDir, env: gitEnv, stdio: 'pipe' });
|
|
1544
|
+
}
|
|
1545
|
+
catch (pushErr) {
|
|
1546
|
+
const pushMsg = pushErr instanceof Error ? pushErr.message : String(pushErr);
|
|
1547
|
+
fileLog({
|
|
1548
|
+
level: 'error', event: 'fix_branch_push_failed', repo: `${owner}/${repoName}`, pr: prNumber,
|
|
1549
|
+
branch: fixBranch, lease: remoteOid ?? 'absent',
|
|
1550
|
+
reason: isLeaseRejection(pushMsg) ? 'lease_rejected' : 'push_failed',
|
|
1551
|
+
error: pushMsg.slice(0, 500),
|
|
1552
|
+
});
|
|
1553
|
+
throw pushErr;
|
|
1554
|
+
}
|
|
1555
|
+
if (remoteOid !== null) {
|
|
1556
|
+
fileLog({ level: 'info', event: 'fix_branch_replaced', repo: `${owner}/${repoName}`, pr: prNumber, branch: fixBranch, previous_sha: remoteOid, sha: newSha });
|
|
1557
|
+
}
|
|
1558
|
+
// Undo this run's push when the follow-up PR turns out not to be creatable after
|
|
1559
|
+
// all. Only a branch this run brought into existence is removed — one that was
|
|
1560
|
+
// already crosscheck's is left at the newer commit rather than deleted out from
|
|
1561
|
+
// under whatever else references it.
|
|
1562
|
+
const rollBackPushedFixBranch = () => {
|
|
1563
|
+
if (remoteOid !== null)
|
|
1564
|
+
return;
|
|
1565
|
+
try {
|
|
1566
|
+
execFileSync('git', ['push', 'origin', '--delete', fixBranch], { cwd: tmpDir, env: gitEnv, stdio: 'pipe' });
|
|
1567
|
+
fileLog({ level: 'info', event: 'fix_branch_rolled_back', repo: `${owner}/${repoName}`, pr: prNumber, branch: fixBranch, sha: newSha });
|
|
1568
|
+
}
|
|
1569
|
+
catch (deleteErr) {
|
|
1570
|
+
fileLog({ level: 'warn', event: 'fix_branch_rollback_failed', repo: `${owner}/${repoName}`, pr: prNumber, branch: fixBranch, error: deleteErr instanceof Error ? deleteErr.message.slice(0, 500) : String(deleteErr) });
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
// The source PR can still merge in the window between the check above and this
|
|
1574
|
+
// point. Asking again here is what keeps the push from becoming a stale fix PR:
|
|
1575
|
+
// the branch is already on the remote, so the answer decides whether it stays.
|
|
1576
|
+
if (await sourcePRHasMerged(octokit, owner, repoName, prNumber)) {
|
|
1577
|
+
rollBackPushedFixBranch();
|
|
1578
|
+
restorePRHeadInClone();
|
|
1579
|
+
skipFix('source_pr_merged');
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1424
1582
|
const fixPrTitle = config.post_review.auto_fix.delivery.pr_title.replace('#{original_pr_title}', pr.title);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
head: fixBranch
|
|
1429
|
-
base: pr.head.ref,
|
|
1430
|
-
title: fixPrTitle,
|
|
1431
|
-
body: [
|
|
1432
|
-
`Auto-fix by crosscheck for CR issues found in #${prNumber}.`,
|
|
1433
|
-
'',
|
|
1434
|
-
`Review: https://github.com/${owner}/${repoName}/pull/${prNumber}`,
|
|
1435
|
-
'',
|
|
1436
|
-
fixAttributionFooter(),
|
|
1437
|
-
].join('\n'),
|
|
1583
|
+
// An earlier round may already have an open PR from this branch; the force-push
|
|
1584
|
+
// above updated it, so opening a second one would be rejected as a duplicate.
|
|
1585
|
+
const { data: existingFixPrs } = await octokit.rest.pulls.list({
|
|
1586
|
+
owner, repo: repoName, state: 'open', head: `${owner}:${fixBranch}`,
|
|
1438
1587
|
});
|
|
1588
|
+
let fixPrNumber;
|
|
1589
|
+
if (existingFixPrs.length > 0) {
|
|
1590
|
+
fixPrNumber = existingFixPrs[0].number;
|
|
1591
|
+
fileLog({ level: 'info', event: 'fix_pr_updated', repo: `${owner}/${repoName}`, pr: prNumber, fix_pr: fixPrNumber, sha: newSha });
|
|
1592
|
+
}
|
|
1593
|
+
else {
|
|
1594
|
+
let fixPr;
|
|
1595
|
+
try {
|
|
1596
|
+
const created = await octokit.rest.pulls.create({
|
|
1597
|
+
owner,
|
|
1598
|
+
repo: repoName,
|
|
1599
|
+
head: fixBranch,
|
|
1600
|
+
base: delivery.base,
|
|
1601
|
+
title: fixPrTitle,
|
|
1602
|
+
body: [
|
|
1603
|
+
autoFixPRIntro(prNumber),
|
|
1604
|
+
'',
|
|
1605
|
+
`Review: https://github.com/${owner}/${repoName}/pull/${prNumber}`,
|
|
1606
|
+
'',
|
|
1607
|
+
fixAttributionFooter(),
|
|
1608
|
+
].join('\n'),
|
|
1609
|
+
});
|
|
1610
|
+
fixPr = created.data;
|
|
1611
|
+
}
|
|
1612
|
+
catch (createErr) {
|
|
1613
|
+
// A merge landing in a repo that deletes its branches takes the base away
|
|
1614
|
+
// between the checks above and this call, and `pulls.create` reports that as
|
|
1615
|
+
// `field: base, code: invalid` — the original failure this fallback exists to
|
|
1616
|
+
// stop, just in a narrower window. Take the branch back out and deliver the
|
|
1617
|
+
// fix as a diff rather than leaving it orphaned.
|
|
1618
|
+
if (!isInvalidBaseError(createErr))
|
|
1619
|
+
throw createErr;
|
|
1620
|
+
rollBackPushedFixBranch();
|
|
1621
|
+
await deliverFixAsComment({
|
|
1622
|
+
event: 'fix_pr_create_failed',
|
|
1623
|
+
reason: 'base_branch_gone_after_push',
|
|
1624
|
+
branch: delivery.base,
|
|
1625
|
+
notice: `${delivery.base} disappeared while the fix branch was being pushed — posting the fix as a diff instead of opening a stale follow-up PR`,
|
|
1626
|
+
explanation: `\`${delivery.base}\` existed when this fix started and was gone by the time the follow-up PR was opened, so this PR merged mid-run. The fix is below rather than in a follow-up PR — a PR cut from the pre-merge state would reintroduce whatever the merged version changed.`,
|
|
1627
|
+
});
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
fixPrNumber = fixPr.number;
|
|
1631
|
+
}
|
|
1439
1632
|
if (config.post_review.auto_fix.delivery.label) {
|
|
1440
1633
|
try {
|
|
1441
1634
|
await octokit.rest.issues.addLabels({
|
|
1442
|
-
owner, repo: repoName, issue_number:
|
|
1635
|
+
owner, repo: repoName, issue_number: fixPrNumber, labels: [config.post_review.auto_fix.delivery.label],
|
|
1443
1636
|
});
|
|
1444
1637
|
}
|
|
1445
1638
|
catch { /* label may not exist in this repo — skip */ }
|
|
1446
1639
|
}
|
|
1447
1640
|
onPhaseChange('fixed ✓', { fixCount: appliedCount, phase: 'fixed', fixTokens: fixTokensUsed });
|
|
1448
|
-
fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'pull_request', fix_pr:
|
|
1641
|
+
fileLog({ level: 'info', event: 'fix_complete', repo: `${owner}/${repoName}`, pr: prNumber, vendor: activeVendor, applied_count: appliedCount, sha: newSha, delivery: 'pull_request', fix_pr: fixPrNumber, tokens_used: fixTokensUsed, skills_activated: activatedSkills.map(skill => skill.name), duration_ms: Date.now() - fixStepStart, ...triggerField });
|
|
1449
1642
|
results[step.name] = { applied_count: appliedCount, tokens_used: fixTokensUsed, vendor: activeVendor };
|
|
1450
1643
|
}
|
|
1451
1644
|
}
|