@link-assistant/hive-mind 2.12.0 → 2.12.2
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/CHANGELOG.md +14 -0
- package/package.json +4 -1
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +14 -174
- package/src/limits.lib.mjs +0 -89
- package/src/models/index.mjs +0 -15
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +0 -90
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/telegram-bot.mjs +0 -66
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
|
@@ -9,14 +9,12 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
|
9
9
|
*
|
|
10
10
|
* @see https://github.com/link-assistant/hive-mind/issues/1190
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
12
|
// Check if use is already defined globally (when imported from solve.mjs)
|
|
14
13
|
// If not, fetch it (when running standalone)
|
|
15
14
|
if (typeof globalThis.use === 'undefined') {
|
|
16
15
|
await ensureUseM();
|
|
17
16
|
}
|
|
18
17
|
const use = globalThis.use;
|
|
19
|
-
|
|
20
18
|
// Use command-stream for consistent $ behavior across runtimes
|
|
21
19
|
const { $: __rawDollar$ } = await use('command-stream');
|
|
22
20
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
@@ -24,19 +22,15 @@ const $ = wrapDollarWithGhRetry(__rawDollar$);
|
|
|
24
22
|
// Import shared library functions
|
|
25
23
|
const lib = await import('./lib.mjs');
|
|
26
24
|
const { log, cleanErrorMessage, formatAligned, formatToolExecutionFailure, extractToolErrorCore, getLogFile } = lib;
|
|
27
|
-
|
|
28
25
|
// Note: We don't use detectAndCountFeedback from solve.feedback.lib.mjs
|
|
29
26
|
// because we have our own non-bot comment detection logic that's more
|
|
30
27
|
// appropriate for auto-restart-until-mergeable mode
|
|
31
|
-
|
|
32
28
|
// Import Sentry integration
|
|
33
29
|
const sentryLib = await import('./sentry.lib.mjs');
|
|
34
30
|
const { reportError } = sentryLib;
|
|
35
|
-
|
|
36
31
|
// Import GitHub merge functions
|
|
37
32
|
const githubMergeLib = await import('./github-merge.lib.mjs');
|
|
38
33
|
const { checkMergePermissions, mergePullRequest, getRepoVisibility, BILLING_LIMIT_ERROR_PATTERN, getDetailedCIStatus, rerunWorkflowRun, getWorkflowRunsForSha, getAllActiveRepoRuns, checkCIConsensus } = githubMergeLib;
|
|
39
|
-
|
|
40
34
|
// Import GitHub functions for log attachment
|
|
41
35
|
const githubLib = await import('./github.lib.mjs');
|
|
42
36
|
const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
@@ -44,52 +38,46 @@ const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
|
44
38
|
// Import shared utilities from the restart-shared module
|
|
45
39
|
const restartShared = await import('./solve.restart-shared.lib.mjs');
|
|
46
40
|
const { checkForUncommittedChanges, getUncommittedChangesDetails, executeToolIteration, buildAutoRestartInstructions, isUsageLimitReached } = restartShared;
|
|
47
|
-
|
|
48
41
|
// Issue #1931: deleted/inaccessible repositories, PRs, issues, and branches
|
|
49
42
|
// are terminal states for long-running watch loops, not retryable CI states.
|
|
50
43
|
const terminalStateLib = await import('./github-terminal-state.lib.mjs');
|
|
51
44
|
const { checkGitHubTerminalState } = terminalStateLib;
|
|
52
|
-
|
|
53
45
|
// Issue #2144: these probes answer with a ~33 KB pull request object and a full
|
|
54
46
|
// issue object on every iteration. Issue #2130 made the helper's own default
|
|
55
47
|
// runner quiet, but passing `$` here bypassed it and the payloads were still
|
|
56
48
|
// mirrored into the attached log. Bind the quiet options to the injected `$`.
|
|
57
49
|
const { quietProbe } = await import('./quiet-probe.lib.mjs');
|
|
58
|
-
|
|
59
50
|
// Issue #2144: a closed linked issue is NOT terminal — it only blocks the final
|
|
60
51
|
// automatic merge. Every stop of this loop is also published as a GitHub comment
|
|
61
52
|
// stating exactly why it stopped.
|
|
62
53
|
const stopReportingLib = await import('./automation-stop-reporting.lib.mjs');
|
|
63
54
|
const { reportAutomationStop } = stopReportingLib;
|
|
64
|
-
|
|
65
55
|
// Import validation functions for time parsing (used for usage limit wait)
|
|
66
56
|
const validation = await import('./solve.validation.lib.mjs');
|
|
67
57
|
const { calculateWaitTime } = validation;
|
|
68
|
-
|
|
69
58
|
// Import configuration (used for limit reset buffer and jitter)
|
|
70
59
|
import { limitReset } from './config.lib.mjs';
|
|
71
|
-
|
|
72
60
|
// Import helper functions extracted for file size management (Issue #1593)
|
|
73
61
|
const autoMergeHelpers = await import('./solve.auto-merge-helpers.lib.mjs');
|
|
74
62
|
const { checkForExistingComment, checkForNonBotComments, checkForIssueMetadataChanges, getMergeBlockers, shouldResetNoRunsCounter, trackAuthenticatedUserCommentsSince, nextMonotonicCheckTime } = autoMergeHelpers;
|
|
75
|
-
|
|
76
63
|
// Issue #1769: cancelled/stale CI re-run failures need a human action stop, not polling forever.
|
|
77
64
|
const cancelledCiRerunLib = await import('./cancelled-ci-rerun.lib.mjs');
|
|
78
65
|
const { buildCancelledCIReviewComment, getRetriggerableWorkflowRuns, shouldStopForCancelledCIReview } = cancelledCiRerunLib;
|
|
79
66
|
|
|
80
67
|
// Issue #1625: Shared marker constants + posting/tracking helpers
|
|
81
68
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
82
|
-
const { READY_TO_MERGE_MARKER, READY_FOR_REVIEW_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
|
|
83
|
-
|
|
69
|
+
const { READY_TO_MERGE_MARKER, READY_FOR_REVIEW_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
|
|
70
|
+
// Issue #2148: in-process usage-limit continuations bypass startWorkSession,
|
|
71
|
+
// so post their session boundary explicitly before invoking `--resume`.
|
|
72
|
+
const sessionLib = await import('./solve.session.lib.mjs');
|
|
73
|
+
const { postWorkSessionStartComment, SESSION_TYPES } = sessionLib;
|
|
84
74
|
const externalReviewLimitLib = await import('./external-review-limit.lib.mjs');
|
|
85
75
|
const { buildReadyForReviewComment } = externalReviewLimitLib;
|
|
86
|
-
|
|
87
76
|
// Issue #1728: Per-iteration working session summary attachment helper
|
|
88
77
|
// Issue #1763: Per-iteration PR ↔ issue link verification (so a clobbered
|
|
89
78
|
// PR body is restored before the next stop condition fires).
|
|
90
79
|
const resultsLib = await import('./solve.results.lib.mjs');
|
|
91
80
|
const { maybeAttachWorkingSessionSummary, ensurePullRequestIssueLink } = resultsLib;
|
|
92
|
-
|
|
93
81
|
// Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
|
|
94
82
|
const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
|
|
95
83
|
const { formatAutoIterationLimit, hasReachedAutoIterationLimit, normalizeAutoIterationLimit, shouldSyncBeforeRestart } = await import('./auto-iteration-limits.lib.mjs');
|
|
@@ -101,14 +89,11 @@ const autoRestartBudget = await import('./auto-restart-budget.lib.mjs');
|
|
|
101
89
|
const { beginAutoRestartBudget, consumeAutoRestartIteration, formatAutoRestartLabel, formatAutoRestartLimit, hasExhaustedAutoRestartBudget } = autoRestartBudget;
|
|
102
90
|
const { failOnAutoRestartBudgetExhausted } = await import('./auto-restart-exhaustion.lib.mjs');
|
|
103
91
|
const { ensurePullRequestBaseBranch } = await import('./solve.pr-base-guard.lib.mjs');
|
|
104
|
-
|
|
105
92
|
// Issue #2119: an empty pull request must not be reported as ready to merge.
|
|
106
93
|
const { buildEmptyPullRequestBlocker, getPullRequestChangeStats } = await import('./pull-request-changes.lib.mjs');
|
|
107
|
-
|
|
108
94
|
// Issue #1895: explicitly close linked issues after merging a PR into a
|
|
109
95
|
// non-default branch, where GitHub does not auto-close them.
|
|
110
96
|
const { ensureLinkedIssueClosedAfterMerge } = await import('./github-issue-auto-close.lib.mjs');
|
|
111
|
-
|
|
112
97
|
const shouldDeleteBranchAfterMerge = argv => argv.autoDeleteBranchOnMerge || argv.deleteBranchAfterMerge || false;
|
|
113
98
|
|
|
114
99
|
/**
|
|
@@ -117,7 +102,6 @@ const shouldDeleteBranchAfterMerge = argv => argv.autoDeleteBranchOnMerge || arg
|
|
|
117
102
|
*/
|
|
118
103
|
export const watchUntilMergeable = async params => {
|
|
119
104
|
const { issueUrl, owner, repo, issueNumber, prNumber, prBranch, branchName, tempDir, argv } = params;
|
|
120
|
-
|
|
121
105
|
const rawWatchInterval = argv.watchInterval || 60; // seconds
|
|
122
106
|
// Issue #1567: Minimum 120s interval to conserve API rate limits while keeping responsiveness
|
|
123
107
|
const MIN_CI_CHECK_INTERVAL_SECONDS = 120;
|
|
@@ -129,25 +113,19 @@ export const watchUntilMergeable = async params => {
|
|
|
129
113
|
// Issue #1503/#1573/#1612: repo-wide action gating is opt-in strict mode.
|
|
130
114
|
// The config default may be bypassed when this module is reused directly, so normalize here.
|
|
131
115
|
const waitForAllRepoActionsFlag = argv.waitForAllActionsInRepositoryBeforeMergeable ?? argv['wait-for-all-actions-in-repository-before-mergeable'] ?? argv.waitForAllActionsInRepositoryBeforeMergable ?? argv['wait-for-all-actions-in-repository-before-mergable'] ?? false;
|
|
132
|
-
|
|
133
116
|
// Track latest session data across all iterations for accurate pricing
|
|
134
117
|
let latestSessionId = null;
|
|
135
118
|
let latestAnthropicCost = null;
|
|
136
|
-
|
|
137
119
|
// Issue #1323: Track actual AI restarts separately from check cycle iterations
|
|
138
120
|
// Issue #2119: the count now lives in the shared budget module, so restarts
|
|
139
121
|
// already spent by the watch loop earlier in this run are counted here too.
|
|
140
122
|
let limitResumeCount = 0;
|
|
141
|
-
|
|
142
123
|
// Issue #1371: In-memory dedup for "Ready to merge" comment (per-session, not all-time)
|
|
143
124
|
let readyToMergeCommentPosted = false;
|
|
144
|
-
|
|
145
125
|
let currentBackoffSeconds = watchInterval;
|
|
146
|
-
|
|
147
126
|
// Issue #1503: Track consecutive "no workflow runs" checks per-SHA (reset on new push)
|
|
148
127
|
let consecutiveNoRunsChecks = 0;
|
|
149
128
|
let lastKnownHeadSha = null;
|
|
150
|
-
|
|
151
129
|
// Issue #1567: Initial cooldown to let CI register and solution logs post
|
|
152
130
|
const INITIAL_COOLDOWN_SECONDS = MIN_CI_CHECK_INTERVAL_SECONDS;
|
|
153
131
|
|
|
@@ -170,26 +148,21 @@ export const watchUntilMergeable = async params => {
|
|
|
170
148
|
await log('');
|
|
171
149
|
await log('Press Ctrl+C to stop watching manually');
|
|
172
150
|
await log('');
|
|
173
|
-
|
|
174
151
|
// Issue #1567: Wait for initial cooldown before first check.
|
|
175
152
|
// This gives CI/CD time to start and solution logs time to be posted.
|
|
176
153
|
await log(formatAligned('⏳', 'Initial cooldown:', `Waiting ${INITIAL_COOLDOWN_SECONDS}s before first check...`));
|
|
177
154
|
await interruptibleSleep(INITIAL_COOLDOWN_SECONDS * 1000);
|
|
178
155
|
await log(formatAligned('✅', 'Cooldown complete:', 'Starting monitoring loop'));
|
|
179
156
|
await log('');
|
|
180
|
-
|
|
181
157
|
let iteration = 0;
|
|
182
158
|
let lastCheckTime = new Date();
|
|
183
|
-
|
|
184
159
|
// Issue #2007: Track the issue title/body across iterations so the
|
|
185
160
|
// restart/resume fallback can detect user edits to those surfaces and deliver
|
|
186
161
|
// them as feedback to the next session. The first check seeds the baseline.
|
|
187
162
|
let issueMetadataSnapshot = null;
|
|
188
|
-
|
|
189
163
|
while (true) {
|
|
190
164
|
iteration++;
|
|
191
165
|
const currentTime = new Date();
|
|
192
|
-
|
|
193
166
|
const terminalState = await checkGitHubTerminalState({
|
|
194
167
|
owner,
|
|
195
168
|
repo,
|
|
@@ -217,7 +190,6 @@ export const watchUntilMergeable = async params => {
|
|
|
217
190
|
await reportAutomationStop({ $, owner, repo, targetNumber: prNumber, reason: terminalState.reason, mode: 'auto-restart-until-mergeable', message: terminalState.message, details: terminalState.details, verbose: argv.verbose, log });
|
|
218
191
|
return { success: false, reason: terminalState.reason, latestSessionId, latestAnthropicCost };
|
|
219
192
|
}
|
|
220
|
-
|
|
221
193
|
// Issue #2144: issue-scoped problems (closed / deleted linked issue) never
|
|
222
194
|
// stop this loop. They are carried to the merge decision below.
|
|
223
195
|
const issueMergeBlockers = terminalState.mergeBlockers || [];
|
|
@@ -226,7 +198,6 @@ export const watchUntilMergeable = async params => {
|
|
|
226
198
|
await log(formatAligned('⚠️', 'Linked issue:', `${blocker.message} Continuing to make the pull request mergeable.`, 2), { level: 'warning' });
|
|
227
199
|
}
|
|
228
200
|
}
|
|
229
|
-
|
|
230
201
|
await log(formatAligned('🔍', `Check #${iteration}:`, currentTime.toLocaleTimeString()));
|
|
231
202
|
|
|
232
203
|
try {
|
|
@@ -250,13 +221,10 @@ export const watchUntilMergeable = async params => {
|
|
|
250
221
|
// so a new "Ready to merge" comment can be posted for the new commit's CI results.
|
|
251
222
|
readyToMergeCommentPosted = false;
|
|
252
223
|
}
|
|
253
|
-
|
|
254
224
|
// Issue #1503: Increment counter; getMergeBlockers uses it as a safety valve
|
|
255
225
|
consecutiveNoRunsChecks++;
|
|
256
|
-
|
|
257
226
|
// Get merge blockers
|
|
258
227
|
const { blockers, noCiConfigured, noCiTriggered, workflowRunConclusions, ciStatus, noWorkflowRunsForCommit } = await getMergeBlockers(owner, repo, prNumber, argv.verbose, consecutiveNoRunsChecks, prBranch);
|
|
259
|
-
|
|
260
228
|
const terminalGitHubBlocker = blockers.find(b => b.type === 'terminal_github_entity_error');
|
|
261
229
|
if (terminalGitHubBlocker) {
|
|
262
230
|
await log('');
|
|
@@ -269,7 +237,6 @@ export const watchUntilMergeable = async params => {
|
|
|
269
237
|
await reportAutomationStop({ $, owner, repo, targetNumber: prNumber, reason: 'terminal_github_entity_error', mode: 'auto-restart-until-mergeable', message: terminalGitHubBlocker.message, details: terminalGitHubBlocker.details, verbose: argv.verbose, log });
|
|
270
238
|
return { success: false, reason: 'terminal_github_entity_error', latestSessionId, latestAnthropicCost };
|
|
271
239
|
}
|
|
272
|
-
|
|
273
240
|
// Issue #1503/#1918: Reset counter when CI checks exist (safety valve only for
|
|
274
241
|
// consecutive "no runs"). Issue #1918: do NOT reset while getMergeBlockers is still
|
|
275
242
|
// waiting for PR-triggered workflow runs to register (noWorkflowRunsForCommit). A
|
|
@@ -283,14 +250,12 @@ export const watchUntilMergeable = async params => {
|
|
|
283
250
|
// CI was definitively determined: either not configured or not triggered.
|
|
284
251
|
// Keep the counter as-is (it reached the safety valve or wasn't needed).
|
|
285
252
|
}
|
|
286
|
-
|
|
287
253
|
// Check for new comments from non-bot users. At this point the AI tool
|
|
288
254
|
// is not executing, so same-account non-tool comments can be trusted as
|
|
289
255
|
// human feedback while known tool comments remain filtered by markers/IDs.
|
|
290
256
|
const { hasNewComments, comments } = await checkForNonBotComments(owner, repo, prNumber, issueNumber, lastCheckTime, argv.verbose, $, {
|
|
291
257
|
trustAuthenticatedUserComments: true,
|
|
292
258
|
});
|
|
293
|
-
|
|
294
259
|
// Issue #2007: Detect issue title/description edits (user-owned feedback
|
|
295
260
|
// surfaces) so the fallback resumes the AI with them. The first iteration
|
|
296
261
|
// seeds the baseline and never reports a change.
|
|
@@ -298,7 +263,6 @@ export const watchUntilMergeable = async params => {
|
|
|
298
263
|
issueMetadataSnapshot = metadataCheck.snapshot || issueMetadataSnapshot;
|
|
299
264
|
const hasIssueMetadataChanges = metadataCheck.changed === true;
|
|
300
265
|
const issueMetadataChanges = metadataCheck.changes || [];
|
|
301
|
-
|
|
302
266
|
// Check for uncommitted changes using shared utility
|
|
303
267
|
const hasUncommittedChanges = await checkForUncommittedChanges(tempDir, argv);
|
|
304
268
|
|
|
@@ -312,7 +276,6 @@ export const watchUntilMergeable = async params => {
|
|
|
312
276
|
await log(formatAligned('ℹ️', 'CI not triggered:', 'Workflows exist but no workflow runs for this commit (fork PR, paths-ignore, workflow conditions)', 2));
|
|
313
277
|
}
|
|
314
278
|
}
|
|
315
|
-
|
|
316
279
|
// Issue #2119: an empty pull request is not "ready to merge". The Kotlin
|
|
317
280
|
// reproduction run posted "✅ Ready to merge - No pending changes" for a
|
|
318
281
|
// pull request whose net diff was empty, so merging it would have closed
|
|
@@ -323,7 +286,6 @@ export const watchUntilMergeable = async params => {
|
|
|
323
286
|
if (isEmptyPullRequest) {
|
|
324
287
|
await log(formatAligned('⚠️', 'PR is empty:', changeStats.placeholderOnly ? 'only the solver placeholder file is in the diff - not treating it as mergeable' : 'net diff contains no files - not treating it as mergeable', 2), { level: 'warning' });
|
|
325
288
|
}
|
|
326
|
-
|
|
327
289
|
// If PR is mergeable, no blockers, no new comments, no issue metadata
|
|
328
290
|
// edits, no uncommitted changes and it actually changes something
|
|
329
291
|
if (blockers.length === 0 && !hasNewComments && !hasIssueMetadataChanges && !hasUncommittedChanges && !isEmptyPullRequest) {
|
|
@@ -335,7 +297,6 @@ export const watchUntilMergeable = async params => {
|
|
|
335
297
|
const DOUBLE_CHECK_DELAY_MS = 10000; // 10 seconds
|
|
336
298
|
await log(formatAligned('🔍', 'Multi-mechanism CI consensus check:', `Waiting ${DOUBLE_CHECK_DELAY_MS / 1000}s then verifying...`, 2));
|
|
337
299
|
await interruptibleSleep(DOUBLE_CHECK_DELAY_MS);
|
|
338
|
-
|
|
339
300
|
// Run multi-mechanism consensus: Check Runs API + Workflow Runs API + Repo-wide actions
|
|
340
301
|
const consensus = await checkCIConsensus({
|
|
341
302
|
owner,
|
|
@@ -347,7 +308,6 @@ export const watchUntilMergeable = async params => {
|
|
|
347
308
|
getDetailedCIStatus,
|
|
348
309
|
getWorkflowRunsForSha,
|
|
349
310
|
});
|
|
350
|
-
|
|
351
311
|
if (!consensus.allAgree) {
|
|
352
312
|
const m = consensus.mechanisms;
|
|
353
313
|
const repoLabel = m.repoActions.skipped ? 'skipped' : `${m.repoActions.count} active`;
|
|
@@ -377,9 +337,7 @@ export const watchUntilMergeable = async params => {
|
|
|
377
337
|
continue;
|
|
378
338
|
}
|
|
379
339
|
}
|
|
380
|
-
|
|
381
340
|
await log(formatAligned('✅', 'PR IS MERGEABLE!', ''));
|
|
382
|
-
|
|
383
341
|
// Issue #2144: the pull request is ready. A closed/unavailable linked
|
|
384
342
|
// issue blocks only the *automatic* merge — the loop already did its
|
|
385
343
|
// job of making the pull request mergeable. Ask the user to reopen the
|
|
@@ -388,7 +346,6 @@ export const watchUntilMergeable = async params => {
|
|
|
388
346
|
await reportAutoMergeBlockedByIssue({ owner, repo, prNumber, issueNumber, mergeBlockers: issueMergeBlockers, verbose: argv.verbose });
|
|
389
347
|
return { success: false, reason: issueMergeBlockers[0].reason, mergeBlockers: issueMergeBlockers, latestSessionId, latestAnthropicCost };
|
|
390
348
|
}
|
|
391
|
-
|
|
392
349
|
if (isAutoMerge) {
|
|
393
350
|
// Attempt to merge the PR
|
|
394
351
|
await log(formatAligned('🔀', 'Auto-merging PR...', ''));
|
|
@@ -401,7 +358,6 @@ export const watchUntilMergeable = async params => {
|
|
|
401
358
|
if (mergeResult.success) {
|
|
402
359
|
await log(formatAligned('🎉', 'PR MERGED SUCCESSFULLY!', ''));
|
|
403
360
|
await log(formatAligned('', 'Pull request:', `#${prNumber} has been auto-merged`, 2));
|
|
404
|
-
|
|
405
361
|
// Post success comment
|
|
406
362
|
try {
|
|
407
363
|
// Issue #1345: Differentiate message when no CI is configured
|
|
@@ -411,7 +367,6 @@ export const watchUntilMergeable = async params => {
|
|
|
411
367
|
} catch {
|
|
412
368
|
// Don't fail if comment posting fails
|
|
413
369
|
}
|
|
414
|
-
|
|
415
370
|
// Issue #1895: when the PR targeted a non-default branch GitHub does
|
|
416
371
|
// not auto-close the linked issue. Close it explicitly so the issue
|
|
417
372
|
// is not left open after its PR merges.
|
|
@@ -427,7 +382,6 @@ export const watchUntilMergeable = async params => {
|
|
|
427
382
|
await log(formatAligned('⚠️', 'Issue auto-close:', `error closing issue #${issueNumber}: ${closeError.message}`, 2), { level: 'warning' });
|
|
428
383
|
}
|
|
429
384
|
}
|
|
430
|
-
|
|
431
385
|
return { success: true, reason: 'auto-merged', latestSessionId, latestAnthropicCost };
|
|
432
386
|
} else {
|
|
433
387
|
await log(formatAligned('⚠️', 'Auto-merge failed:', mergeResult.error || 'Unknown error', 2));
|
|
@@ -437,7 +391,6 @@ export const watchUntilMergeable = async params => {
|
|
|
437
391
|
// Just report that PR is mergeable and exit
|
|
438
392
|
await log(formatAligned('', 'PR is ready to be merged manually', '', 2));
|
|
439
393
|
await log(formatAligned('', 'Exiting auto-restart-until-mergeable mode', '', 2));
|
|
440
|
-
|
|
441
394
|
// Issue #1371: Post success comment only if not already posted in this session.
|
|
442
395
|
// Issue #1567: Also check PR comment history as a cross-process guard.
|
|
443
396
|
// Two layers of deduplication:
|
|
@@ -478,16 +431,13 @@ export const watchUntilMergeable = async params => {
|
|
|
478
431
|
} catch {
|
|
479
432
|
// Don't fail if comment posting fails
|
|
480
433
|
}
|
|
481
|
-
|
|
482
434
|
return { success: true, reason: 'mergeable', latestSessionId, latestAnthropicCost };
|
|
483
435
|
}
|
|
484
436
|
}
|
|
485
|
-
|
|
486
437
|
// Determine if we need to restart
|
|
487
438
|
let shouldRestart = false;
|
|
488
439
|
let restartReason = '';
|
|
489
440
|
let feedbackLines = [];
|
|
490
|
-
|
|
491
441
|
// Reason 1: New comments from non-bot users
|
|
492
442
|
if (hasNewComments) {
|
|
493
443
|
shouldRestart = true;
|
|
@@ -508,7 +458,6 @@ export const watchUntilMergeable = async params => {
|
|
|
508
458
|
feedbackLines.push('');
|
|
509
459
|
feedbackLines.push('Implement the requested change and commit it to the pull request branch. Do not report the work as done while the diff is empty.');
|
|
510
460
|
}
|
|
511
|
-
|
|
512
461
|
// Issue #2007: Reason 1b: Issue title/description edited by the user.
|
|
513
462
|
if (hasIssueMetadataChanges) {
|
|
514
463
|
shouldRestart = true;
|
|
@@ -523,7 +472,6 @@ export const watchUntilMergeable = async params => {
|
|
|
523
472
|
feedbackLines.push('');
|
|
524
473
|
feedbackLines.push('Please re-read the updated issue and make sure your solution still matches the requirements.');
|
|
525
474
|
}
|
|
526
|
-
|
|
527
475
|
// Issue #1314: Check for billing limit errors BEFORE regular CI failures
|
|
528
476
|
// Billing limits require human intervention and should NOT trigger AI restarts
|
|
529
477
|
const billingBlocker = blockers.find(b => b.type === 'billing_limit');
|
|
@@ -533,15 +481,12 @@ export const watchUntilMergeable = async params => {
|
|
|
533
481
|
await log(formatAligned('', 'Affected jobs:', billingBlocker.details.join(', '), 2));
|
|
534
482
|
await log(formatAligned('', 'All jobs affected:', billingBlocker.allJobsAffected ? 'Yes' : 'No', 2));
|
|
535
483
|
await log('');
|
|
536
|
-
|
|
537
484
|
// Check if this is a private repository
|
|
538
485
|
const repoInfo = await getRepoVisibility(owner, repo, argv.verbose);
|
|
539
|
-
|
|
540
486
|
if (repoInfo.isPrivate) {
|
|
541
487
|
// For private repos, human intervention is required - stop and post comment
|
|
542
488
|
await log(formatAligned('🛑', 'STOPPING', 'Private repository - billing limit requires human intervention'));
|
|
543
489
|
await log(formatAligned('', 'Action required:', "Check the 'Billing & plans' section in your GitHub settings", 2));
|
|
544
|
-
|
|
545
490
|
// Post comment explaining the billing limit issue
|
|
546
491
|
try {
|
|
547
492
|
const commentBody = `## 💳 GitHub Actions Billing Limit Reached
|
|
@@ -576,20 +521,17 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
576
521
|
});
|
|
577
522
|
await log(formatAligned('', '⚠️ Could not post comment to PR', '', 2));
|
|
578
523
|
}
|
|
579
|
-
|
|
580
524
|
return { success: false, reason: 'billing_limit', latestSessionId, latestAnthropicCost };
|
|
581
525
|
} else {
|
|
582
526
|
// For public repos (unusual case), apply exponential backoff and wait
|
|
583
527
|
// Public repos typically have unlimited free CI, so this is unexpected
|
|
584
528
|
await log(formatAligned('⏳', 'Public repository with billing limit (unusual)', 'Applying exponential backoff'));
|
|
585
529
|
await log(formatAligned('', 'Next check in:', `${currentBackoffSeconds} seconds`, 2));
|
|
586
|
-
|
|
587
530
|
// Don't trigger AI restart - just wait and check again
|
|
588
531
|
// The backoff will be applied at the end of the loop
|
|
589
532
|
currentBackoffSeconds = Math.min(currentBackoffSeconds * 2, 3600); // Max 1 hour
|
|
590
533
|
}
|
|
591
534
|
}
|
|
592
|
-
|
|
593
535
|
// Issue #1314: Handle cancelled CI/CD checks - re-trigger them instead of restarting AI
|
|
594
536
|
// Cancelled checks (e.g., manually cancelled, cancelled by another workflow) should be
|
|
595
537
|
// re-triggered automatically. We should NOT restart the AI for these.
|
|
@@ -612,18 +554,15 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
612
554
|
let rerunTriggered = false;
|
|
613
555
|
let rerunAttempted = false;
|
|
614
556
|
const rerunFailures = [];
|
|
615
|
-
|
|
616
557
|
if (sha) {
|
|
617
558
|
runs = await getWorkflowRunsForSha(owner, repo, sha, argv.verbose);
|
|
618
559
|
retriggerable = getRetriggerableWorkflowRuns(runs);
|
|
619
|
-
|
|
620
560
|
if (retriggerable.length === 0) {
|
|
621
561
|
await log(formatAligned('', '⚠️ No cancelled/stale workflow run found for this SHA', '', 2));
|
|
622
562
|
rerunFailures.push({
|
|
623
563
|
error: 'No cancelled/stale workflow run was found for this commit SHA.',
|
|
624
564
|
});
|
|
625
565
|
}
|
|
626
|
-
|
|
627
566
|
for (const run of retriggerable) {
|
|
628
567
|
await log(formatAligned('', `Re-triggering workflow "${run.name}" (${run.id})...`, '', 2));
|
|
629
568
|
rerunAttempted = true;
|
|
@@ -636,7 +575,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
636
575
|
rerunFailures.push({ run, error: rerunResult.error });
|
|
637
576
|
}
|
|
638
577
|
}
|
|
639
|
-
|
|
640
578
|
if (rerunTriggered) {
|
|
641
579
|
await log(formatAligned('⏳', 'Waiting for re-triggered CI to complete...', '', 2));
|
|
642
580
|
// Don't restart AI - just wait for re-triggered jobs to complete
|
|
@@ -648,10 +586,8 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
648
586
|
error: 'Cancelled CI blocker did not include a commit SHA, so automatic workflow re-run could not identify the run.',
|
|
649
587
|
});
|
|
650
588
|
}
|
|
651
|
-
|
|
652
589
|
if (shouldStopForCancelledCIReview({ retriggerableRuns: retriggerable, rerunTriggered, rerunFailures })) {
|
|
653
590
|
await log(formatAligned('🛑', 'CANCELLED CI/CD NEEDS HUMAN REVIEW', 'Automatic re-run could not be started'));
|
|
654
|
-
|
|
655
591
|
try {
|
|
656
592
|
const commentBody = buildCancelledCIReviewComment({
|
|
657
593
|
blocker: cancelledBlocker,
|
|
@@ -672,7 +608,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
672
608
|
});
|
|
673
609
|
await log(formatAligned('', '⚠️ Could not post cancelled CI review comment to PR', '', 2));
|
|
674
610
|
}
|
|
675
|
-
|
|
676
611
|
return { success: false, reason: 'ci_cancelled_requires_review', latestSessionId, latestAnthropicCost };
|
|
677
612
|
}
|
|
678
613
|
// Don't set shouldRestart for cancelled checks - wait for re-triggered jobs instead
|
|
@@ -692,7 +627,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
692
627
|
await log(formatAligned('', 'Check not executed:', detail, 2));
|
|
693
628
|
}
|
|
694
629
|
await log(formatAligned('', 'Action:', 'Stopping auto-restart without starting another AI session', 2));
|
|
695
|
-
|
|
696
630
|
try {
|
|
697
631
|
const commentSignature = `## 🟡 ${READY_FOR_REVIEW_MARKER}`;
|
|
698
632
|
const hasExistingReadyForReviewComment = await checkForExistingComment(owner, repo, prNumber, commentSignature, argv.verbose);
|
|
@@ -716,10 +650,8 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
716
650
|
});
|
|
717
651
|
await log(formatAligned('', '⚠️ Could not post ready-for-review comment to PR', '', 2));
|
|
718
652
|
}
|
|
719
|
-
|
|
720
653
|
return { success: false, reason: 'external_review_limit', latestSessionId, latestAnthropicCost };
|
|
721
654
|
}
|
|
722
|
-
|
|
723
655
|
if (ciBlocker && !billingBlocker) {
|
|
724
656
|
shouldRestart = true;
|
|
725
657
|
restartReason = restartReason ? `${restartReason}; CI failures` : 'CI failures detected';
|
|
@@ -736,7 +668,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
736
668
|
feedbackLines.push('');
|
|
737
669
|
feedbackLines.push('Please fix the failing CI checks.');
|
|
738
670
|
}
|
|
739
|
-
|
|
740
671
|
// Reason 3: Merge conflicts or other merge issues
|
|
741
672
|
const mergeBlocker = blockers.find(b => b.type === 'not_mergeable');
|
|
742
673
|
if (mergeBlocker && mergeBlocker.message.includes('conflicts')) {
|
|
@@ -747,12 +678,10 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
747
678
|
feedbackLines.push('');
|
|
748
679
|
feedbackLines.push('Please resolve the merge conflicts.');
|
|
749
680
|
}
|
|
750
|
-
|
|
751
681
|
// Reason 4: Uncommitted changes
|
|
752
682
|
if (hasUncommittedChanges) {
|
|
753
683
|
shouldRestart = true;
|
|
754
684
|
restartReason = restartReason ? `${restartReason}; Uncommitted changes` : 'Uncommitted changes detected';
|
|
755
|
-
|
|
756
685
|
// Get uncommitted changes for display using shared utility
|
|
757
686
|
const changes = await getUncommittedChangesDetails(tempDir);
|
|
758
687
|
feedbackLines.push('📝 Uncommitted changes detected:');
|
|
@@ -764,7 +693,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
764
693
|
feedbackLines.push('1. COMMITTING them if they are part of the solution (git add + git commit + git push)');
|
|
765
694
|
feedbackLines.push('2. REVERTING them if they are not needed (git checkout -- <file> or git clean -fd)');
|
|
766
695
|
}
|
|
767
|
-
|
|
768
696
|
if (shouldRestart) {
|
|
769
697
|
// Issue #2119: the run-wide budget is exhausted (it may already have been
|
|
770
698
|
// spent by the watch loop). Fail and auto-commit through the same shared
|
|
@@ -788,11 +716,9 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
788
716
|
|
|
789
717
|
// Add standard instructions for auto-restart-until-mergeable mode using shared utility
|
|
790
718
|
feedbackLines.push(...buildAutoRestartInstructions());
|
|
791
|
-
|
|
792
719
|
// Get PR merge state status
|
|
793
720
|
const prStateResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.mergeStateStatus'`;
|
|
794
721
|
const mergeStateStatus = prStateResult.code === 0 ? prStateResult.stdout.toString().trim() : null;
|
|
795
|
-
|
|
796
722
|
// Issue #1572: Sync clean local branches with remote before restarting to avoid push failures.
|
|
797
723
|
// Issue #1664: Do not run git pull over an unfinished merge or other uncommitted state.
|
|
798
724
|
// The tool must see that state and either commit, continue, abort, or otherwise resolve it.
|
|
@@ -818,15 +744,12 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
818
744
|
} else {
|
|
819
745
|
await log(formatAligned('↪️', 'Skipping branch sync:', 'Local uncommitted/merge state must be resolved by the AI session', 2));
|
|
820
746
|
}
|
|
821
|
-
|
|
822
747
|
// Issue #1323: Increment restart count only when a tool execution is about to start.
|
|
823
748
|
// Issue #2119: claim it from the run-wide shared budget.
|
|
824
749
|
const restartCount = consumeAutoRestartIteration();
|
|
825
|
-
|
|
826
750
|
await log(formatAligned('🔄', 'RESTART TRIGGERED:', restartReason));
|
|
827
751
|
await log(formatAligned('', 'Restart iteration:', formatAutoRestartLabel(restartCount), 2));
|
|
828
752
|
await log('');
|
|
829
|
-
|
|
830
753
|
// Post a comment to PR about the restart after preflight succeeds, so every
|
|
831
754
|
// posted restart notification corresponds to an actual tool session.
|
|
832
755
|
try {
|
|
@@ -848,15 +771,13 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
848
771
|
});
|
|
849
772
|
await log(formatAligned('', '⚠️ Could not post comment to PR', '', 2));
|
|
850
773
|
}
|
|
851
|
-
|
|
852
774
|
// Execute the AI tool using shared utility
|
|
853
775
|
await log(formatAligned('🔄', 'Restarting:', `Running ${argv.tool.toUpperCase()} to address issues...`));
|
|
854
|
-
|
|
855
776
|
// Issue #1728: Scope the AI-comment check that gates --auto-attach-solution-summary
|
|
856
777
|
// to comments posted during *this* iteration only, not across the whole watch loop.
|
|
857
778
|
const iterationStartTime = new Date();
|
|
858
779
|
|
|
859
|
-
|
|
780
|
+
let toolResult = await executeToolIteration({
|
|
860
781
|
issueUrl,
|
|
861
782
|
owner,
|
|
862
783
|
repo,
|
|
@@ -868,7 +789,7 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
868
789
|
feedbackLines,
|
|
869
790
|
argv,
|
|
870
791
|
});
|
|
871
|
-
|
|
792
|
+
let resumedAfterUsageLimit = false;
|
|
872
793
|
if (!toolResult.success) {
|
|
873
794
|
// Issue #1356: Check for usage limit errors FIRST (most specific)
|
|
874
795
|
// When usage limit is reached, wait for limitResetTime + buffer + jitter,
|
|
@@ -885,7 +806,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
885
806
|
await reportAutomationStop({ $, owner, repo, targetNumber: prNumber, reason: 'auto_resume_limit_reached', mode: 'auto-restart-until-mergeable', message: `Stopped after ${limitResumeCount} usage-limit continuation${limitResumeCount !== 1 ? 's' : ''} (limit: ${formatAutoIterationLimit(maxAutoResumeIterations)}).`, verbose: argv.verbose, log });
|
|
886
807
|
return { success: false, reason: 'auto_resume_limit_reached', latestSessionId, latestAnthropicCost };
|
|
887
808
|
}
|
|
888
|
-
|
|
889
809
|
limitResumeCount++;
|
|
890
810
|
const resumeSessionId = toolResult.sessionId;
|
|
891
811
|
const resetTime = toolResult.limitResetTime;
|
|
@@ -896,14 +816,12 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
896
816
|
const bufferMinutes = Math.round(bufferMs / 60000);
|
|
897
817
|
const jitterSeconds = Math.round(jitterMs / 1000);
|
|
898
818
|
const waitMinutes = Math.round(waitMs / 60000);
|
|
899
|
-
|
|
900
819
|
// Issue #1570: Calculate the actual resume time for user display
|
|
901
820
|
const resumeDate = new Date(Date.now() + waitMs);
|
|
902
821
|
const resumeTimeUTC = resumeDate
|
|
903
822
|
.toISOString()
|
|
904
823
|
.replace('T', ' ')
|
|
905
824
|
.replace(/\.\d+Z$/, ' UTC');
|
|
906
|
-
|
|
907
825
|
await log('');
|
|
908
826
|
await log(formatAligned('⏳', 'USAGE LIMIT REACHED', ''));
|
|
909
827
|
await log(formatAligned('', 'Reset time:', resetTime || 'Unknown', 2));
|
|
@@ -915,7 +833,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
915
833
|
await log(formatAligned('', 'Session ID:', resumeSessionId, 2));
|
|
916
834
|
}
|
|
917
835
|
await log('');
|
|
918
|
-
|
|
919
836
|
// Issue #1570: Post a GitHub comment to notify the user about the usage limit delay.
|
|
920
837
|
// This follows the same pattern as solve.watch.lib.mjs to ensure consistent user experience.
|
|
921
838
|
const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
|
|
@@ -940,7 +857,7 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
940
857
|
limitResetTime: resetTime,
|
|
941
858
|
toolName: `Anthropic ${(argv.tool || 'claude').charAt(0).toUpperCase() + (argv.tool || 'claude').slice(1)} Code`,
|
|
942
859
|
isAutoResumeEnabled: true,
|
|
943
|
-
autoResumeMode: '
|
|
860
|
+
autoResumeMode: 'resume',
|
|
944
861
|
requestedModel: argv.originalModel || argv.model,
|
|
945
862
|
tool: argv.tool || 'claude',
|
|
946
863
|
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
@@ -960,16 +877,26 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
960
877
|
await log(formatAligned('', `⚠️ Usage limit comment upload error: ${cleanErrorMessage(commentError)}`, '', 2));
|
|
961
878
|
}
|
|
962
879
|
}
|
|
963
|
-
|
|
964
880
|
// Wait until the limit resets
|
|
965
881
|
await interruptibleSleep(waitMs);
|
|
966
|
-
|
|
967
882
|
await log(formatAligned('✅', 'Usage limit wait complete', 'Resuming session...'));
|
|
968
883
|
await log('');
|
|
969
884
|
|
|
970
885
|
// Resume the session: execute with --resume <sessionId> and a "Continue" prompt
|
|
971
886
|
// This preserves context and the system message from the original session
|
|
972
887
|
if (resumeSessionId) {
|
|
888
|
+
// Issue #2148: this continuation stays in the same Claude session,
|
|
889
|
+
// but it does not pass through the top-level startWorkSession path.
|
|
890
|
+
// Publish the existing auto-resume marker at the real boundary.
|
|
891
|
+
await postWorkSessionStartComment({
|
|
892
|
+
owner,
|
|
893
|
+
repo,
|
|
894
|
+
prNumber,
|
|
895
|
+
$,
|
|
896
|
+
log,
|
|
897
|
+
formatAligned,
|
|
898
|
+
sessionType: SESSION_TYPES.AUTO_RESUME,
|
|
899
|
+
});
|
|
973
900
|
const resumeArgv = { ...argv, resume: resumeSessionId };
|
|
974
901
|
const resumeResult = await executeToolIteration({
|
|
975
902
|
issueUrl,
|
|
@@ -983,14 +910,12 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
983
910
|
feedbackLines: ['Continue'],
|
|
984
911
|
argv: resumeArgv,
|
|
985
912
|
});
|
|
986
|
-
|
|
987
913
|
if (resumeResult.success) {
|
|
988
|
-
//
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
914
|
+
// Issue #2148: feed the resumed result into the same success
|
|
915
|
+
// path as every other iteration. That path publishes the
|
|
916
|
+
// summary and log, verifies the issue link, and tracks comments.
|
|
917
|
+
toolResult = resumeResult;
|
|
918
|
+
resumedAfterUsageLimit = true;
|
|
994
919
|
await log(formatAligned('✅', `${argv.tool.toUpperCase()} resume completed:`, 'Checking if PR is now mergeable...'));
|
|
995
920
|
} else if (isUsageLimitReached(resumeResult)) {
|
|
996
921
|
// Hit the limit again immediately after resume — store for next outer iteration
|
|
@@ -1043,62 +968,64 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1043
968
|
// No session ID available — cannot resume, restart fresh in next iteration
|
|
1044
969
|
await log(formatAligned('⚠️', 'No session ID for resume', 'Will restart fresh in next check cycle', 2));
|
|
1045
970
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
971
|
+
if (!toolResult.success) {
|
|
972
|
+
lastCheckTime = new Date();
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
1049
975
|
}
|
|
1050
|
-
|
|
1051
976
|
// Any other failure (not usage limit): stop the auto-restart loop
|
|
1052
977
|
// Per reviewer feedback: non-limit failures should fail and stop attempts
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
978
|
+
if (!toolResult.success) {
|
|
979
|
+
await log('');
|
|
980
|
+
await log(formatAligned('❌', `${argv.tool.toUpperCase()} EXECUTION FAILED`, ''));
|
|
981
|
+
// Issue #1845: surface the core error in the terminal, not just in the GitHub log.
|
|
982
|
+
await log(formatAligned('', 'Error details:', extractToolErrorCore({ toolResult }) || 'Unknown error', 2));
|
|
983
|
+
await log(formatAligned('', 'Action:', 'Stopping auto-restart — tool execution failed', 2));
|
|
984
|
+
// Issue #1439: Attach failure log before stopping, so user can see what happened
|
|
985
|
+
const shouldAttachLogsOnFail = argv.attachLogs || argv['attach-logs'];
|
|
986
|
+
if (prNumber && shouldAttachLogsOnFail) {
|
|
987
|
+
try {
|
|
988
|
+
const logFile = getLogFile();
|
|
989
|
+
if (logFile) {
|
|
990
|
+
await attachLogToGitHub({
|
|
991
|
+
logFile,
|
|
992
|
+
targetType: 'pr',
|
|
993
|
+
targetNumber: prNumber,
|
|
994
|
+
owner,
|
|
995
|
+
repo,
|
|
996
|
+
$,
|
|
997
|
+
log,
|
|
998
|
+
sanitizeLogContent,
|
|
999
|
+
verbose: argv.verbose,
|
|
1000
|
+
errorMessage: formatToolExecutionFailure({ tool: argv.tool, toolResult }),
|
|
1001
|
+
sessionId: latestSessionId,
|
|
1002
|
+
tempDir,
|
|
1003
|
+
requestedModel: argv.originalModel || argv.model,
|
|
1004
|
+
tool: argv.tool || 'claude',
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
} catch (logUploadError) {
|
|
1008
|
+
reportError(logUploadError, {
|
|
1009
|
+
context: 'attach_auto_restart_failure_log',
|
|
1010
|
+
prNumber,
|
|
1068
1011
|
owner,
|
|
1069
1012
|
repo,
|
|
1070
|
-
|
|
1071
|
-
log,
|
|
1072
|
-
sanitizeLogContent,
|
|
1073
|
-
verbose: argv.verbose,
|
|
1074
|
-
errorMessage: formatToolExecutionFailure({ tool: argv.tool, toolResult }),
|
|
1075
|
-
sessionId: latestSessionId,
|
|
1076
|
-
tempDir,
|
|
1077
|
-
requestedModel: argv.originalModel || argv.model,
|
|
1078
|
-
tool: argv.tool || 'claude',
|
|
1013
|
+
operation: 'upload_failure_log',
|
|
1079
1014
|
});
|
|
1015
|
+
await log(formatAligned('', `⚠️ Failure log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
|
|
1080
1016
|
}
|
|
1081
|
-
} catch (logUploadError) {
|
|
1082
|
-
reportError(logUploadError, {
|
|
1083
|
-
context: 'attach_auto_restart_failure_log',
|
|
1084
|
-
prNumber,
|
|
1085
|
-
owner,
|
|
1086
|
-
repo,
|
|
1087
|
-
operation: 'upload_failure_log',
|
|
1088
|
-
});
|
|
1089
|
-
await log(formatAligned('', `⚠️ Failure log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
|
|
1090
1017
|
}
|
|
1018
|
+
await reportAutomationStop({ $, owner, repo, targetNumber: prNumber, reason: 'tool_failure', mode: 'auto-restart-until-mergeable', message: extractToolErrorCore({ toolResult }) || formatToolExecutionFailure({ tool: argv.tool, toolResult }), verbose: argv.verbose, log });
|
|
1019
|
+
return { success: false, reason: 'tool_failure', latestSessionId, latestAnthropicCost };
|
|
1091
1020
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
} else {
|
|
1021
|
+
}
|
|
1022
|
+
if (toolResult.success) {
|
|
1095
1023
|
// Success - capture latest session data
|
|
1096
1024
|
currentBackoffSeconds = watchInterval;
|
|
1097
1025
|
if (toolResult.sessionId) {
|
|
1098
1026
|
latestSessionId = toolResult.sessionId;
|
|
1099
1027
|
latestAnthropicCost = toolResult.anthropicTotalCostUSD;
|
|
1100
1028
|
}
|
|
1101
|
-
|
|
1102
1029
|
// Issue #1508: Compute budget stats for auto-restart-until-mergeable log comment.
|
|
1103
1030
|
// Issue #2132: shared with the top-level run and the watch loop via
|
|
1104
1031
|
// buildSessionBudgetStatsData, so every working session derives its own
|
|
@@ -1113,7 +1040,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1113
1040
|
subAgentCalls: toolResult.subAgentCalls || null,
|
|
1114
1041
|
pricingInfo: toolResult.pricingInfo || null,
|
|
1115
1042
|
});
|
|
1116
|
-
|
|
1117
1043
|
// Issue #1761: Post the working session **summary** BEFORE uploading
|
|
1118
1044
|
// the working session **log** so the summary always appears above
|
|
1119
1045
|
// the log in PR comment chronological order. The summary acts as a
|
|
@@ -1164,7 +1090,8 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1164
1090
|
// Issue #1323: Use the restart count (actual AI executions) instead of iteration (check cycles)
|
|
1165
1091
|
// Issue #2119: `N/M` like every other auto-restart label, so the
|
|
1166
1092
|
// limit is visible in the log title too.
|
|
1167
|
-
const
|
|
1093
|
+
const autoResumeLabel = maxAutoResumeIterations === 0 ? `${limitResumeCount}` : `${limitResumeCount}/${maxAutoResumeIterations}`;
|
|
1094
|
+
const customTitle = resumedAfterUsageLimit ? `⏰ ${AUTO_RESUME_ON_LIMIT_RESET_MARKER} ${autoResumeLabel} Log` : `🔄 ${AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER} ${formatAutoRestartLabel()}`;
|
|
1168
1095
|
await attachLogToGitHub({
|
|
1169
1096
|
logFile,
|
|
1170
1097
|
targetType: 'pr',
|
|
@@ -1202,7 +1129,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1202
1129
|
await log(formatAligned('', `⚠️ Log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
|
|
1203
1130
|
}
|
|
1204
1131
|
}
|
|
1205
|
-
|
|
1206
1132
|
// Issue #1763: Re-verify the PR body contains a closing keyword for
|
|
1207
1133
|
// the issue after every auto-restart-until-mergeable iteration. The
|
|
1208
1134
|
// AI agent can rewrite the PR description mid-session and any
|
|
@@ -1231,11 +1157,9 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1231
1157
|
await log(formatAligned('', `⚠️ PR issue link check error: ${cleanErrorMessage(issueLinkError)}`, '', 2));
|
|
1232
1158
|
}
|
|
1233
1159
|
}
|
|
1234
|
-
|
|
1235
1160
|
await log('');
|
|
1236
1161
|
await log(formatAligned('✅', `${argv.tool.toUpperCase()} execution completed:`, 'Checking if PR is now mergeable...'));
|
|
1237
1162
|
}
|
|
1238
|
-
|
|
1239
1163
|
// Issue #1827: Register every comment the authenticated account posted
|
|
1240
1164
|
// during this AI session (free-form status comments like "✅ CI now
|
|
1241
1165
|
// green" the agent writes itself, which bypass postTrackedComment and
|
|
@@ -1255,7 +1179,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1255
1179
|
operation: 'track_session_comments',
|
|
1256
1180
|
});
|
|
1257
1181
|
}
|
|
1258
|
-
|
|
1259
1182
|
// Update last check time after restart
|
|
1260
1183
|
lastCheckTime = new Date();
|
|
1261
1184
|
} else if (blockers.length > 0) {
|
|
@@ -1264,7 +1187,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1264
1187
|
const pendingBlocker = blockers.find(b => b.type === 'ci_pending');
|
|
1265
1188
|
const cancelledOnly = blockers.every(b => b.type === 'ci_cancelled' || b.type === 'ci_pending');
|
|
1266
1189
|
const cancelledBlocker = blockers.find(b => b.type === 'ci_cancelled');
|
|
1267
|
-
|
|
1268
1190
|
// Issue #1712: When `details` contain URLs (which they now always do for ci_pending /
|
|
1269
1191
|
// ci_cancelled blockers), comma-joining them produces an unreadable single-line wall
|
|
1270
1192
|
// of text. Render the first detail inline (with the message as the header) and any
|
|
@@ -1284,7 +1206,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1284
1206
|
}
|
|
1285
1207
|
})();
|
|
1286
1208
|
};
|
|
1287
|
-
|
|
1288
1209
|
if (cancelledOnly && cancelledBlocker) {
|
|
1289
1210
|
await renderBlocker('🔄', 'Waiting for re-triggered CI:', cancelledBlocker);
|
|
1290
1211
|
} else if (pendingBlocker) {
|
|
@@ -1295,7 +1216,6 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1295
1216
|
} else {
|
|
1296
1217
|
await log(formatAligned('', 'No action needed', 'Continuing to monitor...', 2));
|
|
1297
1218
|
}
|
|
1298
|
-
|
|
1299
1219
|
// Issue #1827: Advance the check window monotonically — never move it
|
|
1300
1220
|
// backwards. In the restart branch above, lastCheckTime was already set
|
|
1301
1221
|
// to a moment *after* the AI session (and after any comments the agent
|
|
@@ -1325,33 +1245,27 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
1325
1245
|
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
1326
1246
|
}
|
|
1327
1247
|
};
|
|
1328
|
-
|
|
1329
1248
|
// Issue #2144: the one-shot `--auto-merge` attempt moved to its own module so
|
|
1330
1249
|
// both files stay under the 1500-line limit. Re-exported for API compatibility.
|
|
1331
1250
|
const autoMergeAttempt = await import('./solve.auto-merge-attempt.lib.mjs');
|
|
1332
1251
|
export const { attemptAutoMerge, reportAutoMergeBlockedByIssue } = autoMergeAttempt;
|
|
1333
|
-
|
|
1334
1252
|
/**
|
|
1335
1253
|
* Start auto-restart-until-mergeable mode
|
|
1336
1254
|
*/
|
|
1337
1255
|
export const startAutoRestartUntilMergeable = async params => {
|
|
1338
1256
|
const { argv, owner, repo, prNumber } = params;
|
|
1339
|
-
|
|
1340
1257
|
// Determine the mode
|
|
1341
1258
|
const isAutoMerge = argv.autoMerge || false;
|
|
1342
1259
|
const isAutoRestartUntilMergeable = argv.autoRestartUntilMergeable || false;
|
|
1343
|
-
|
|
1344
1260
|
if (!isAutoMerge && !isAutoRestartUntilMergeable) {
|
|
1345
1261
|
return null; // Neither mode enabled
|
|
1346
1262
|
}
|
|
1347
|
-
|
|
1348
1263
|
if (!prNumber) {
|
|
1349
1264
|
await log('');
|
|
1350
1265
|
await log(formatAligned('⚠️', 'Auto-restart-until-mergeable:', 'Requires a pull request'));
|
|
1351
1266
|
await log(formatAligned('', 'Note:', 'This mode only works with existing PRs', 2));
|
|
1352
1267
|
return null;
|
|
1353
1268
|
}
|
|
1354
|
-
|
|
1355
1269
|
await ensurePullRequestBaseBranch({
|
|
1356
1270
|
owner,
|
|
1357
1271
|
repo,
|
|
@@ -1363,7 +1277,6 @@ export const startAutoRestartUntilMergeable = async params => {
|
|
|
1363
1277
|
onMismatch: isAutoMerge ? 'throw' : 'restore',
|
|
1364
1278
|
operation: isAutoMerge ? 'auto-merge' : 'auto-restart-until-mergeable',
|
|
1365
1279
|
});
|
|
1366
|
-
|
|
1367
1280
|
// Issue #1226: Check if running in fork mode — auto-merge cannot work without write access
|
|
1368
1281
|
if (argv.fork && isAutoMerge) {
|
|
1369
1282
|
await log('');
|
|
@@ -1387,10 +1300,8 @@ export const startAutoRestartUntilMergeable = async params => {
|
|
|
1387
1300
|
} catch {
|
|
1388
1301
|
// Don't fail if comment posting fails
|
|
1389
1302
|
}
|
|
1390
|
-
|
|
1391
1303
|
return { success: false, reason: 'fork_no_write_access' };
|
|
1392
1304
|
}
|
|
1393
|
-
|
|
1394
1305
|
// Issue #1226: Verify merge permissions before entering the auto-merge/restart loop
|
|
1395
1306
|
if (isAutoMerge && owner && repo) {
|
|
1396
1307
|
const { canMerge, permission } = await checkMergePermissions(owner, repo, argv.verbose);
|
|
@@ -1401,7 +1312,6 @@ export const startAutoRestartUntilMergeable = async params => {
|
|
|
1401
1312
|
await log(formatAligned('', 'Required:', 'push, maintain, or admin access', 2));
|
|
1402
1313
|
await log(formatAligned('', 'Action:', 'PR is ready for manual merge by a repository maintainer', 2));
|
|
1403
1314
|
await log('');
|
|
1404
|
-
|
|
1405
1315
|
// Issue #1323: Post a comment to the PR notifying the maintainer (with deduplication)
|
|
1406
1316
|
try {
|
|
1407
1317
|
const readyToMergeSignature = `## ✅ ${READY_TO_MERGE_MARKER}`;
|
|
@@ -1417,20 +1327,16 @@ export const startAutoRestartUntilMergeable = async params => {
|
|
|
1417
1327
|
} catch {
|
|
1418
1328
|
// Don't fail if comment posting fails
|
|
1419
1329
|
}
|
|
1420
|
-
|
|
1421
1330
|
return { success: false, reason: 'insufficient_permissions' };
|
|
1422
1331
|
}
|
|
1423
1332
|
}
|
|
1424
|
-
|
|
1425
1333
|
// If --auto-merge implies --auto-restart-until-mergeable
|
|
1426
1334
|
if (isAutoMerge) {
|
|
1427
1335
|
argv.autoRestartUntilMergeable = true;
|
|
1428
1336
|
}
|
|
1429
|
-
|
|
1430
1337
|
// Start the watch loop
|
|
1431
1338
|
return await watchUntilMergeable(params);
|
|
1432
1339
|
};
|
|
1433
|
-
|
|
1434
1340
|
export default {
|
|
1435
1341
|
watchUntilMergeable,
|
|
1436
1342
|
attemptAutoMerge,
|