@link-assistant/hive-mind 1.35.12 → 1.36.1
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
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.36.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 74bf211: fix false positive 'Ready to merge' by adding workflow run grace period (Issue #1480)
|
|
8
|
+
|
|
9
|
+
## 1.36.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 3adbf2b: feat: add --auto-report-issue and --disable-report-issue flags for non-interactive error reporting (Issue #1484)
|
|
14
|
+
- Add `--auto-report-issue` flag that automatically creates a GitHub issue on failure without prompting.
|
|
15
|
+
The auto-reported issue includes error details, logs, and case study analysis instructions in the body.
|
|
16
|
+
Issue is labeled as `bug`.
|
|
17
|
+
- Add `--disable-report-issue` flag that completely disables error issue creation (no prompt, no auto-creation).
|
|
18
|
+
Takes precedence over `--auto-report-issue` if both are specified.
|
|
19
|
+
- Default behavior (neither flag) preserves the existing interactive y/n prompt.
|
|
20
|
+
- Both flags are automatically available as passthrough options in hive and TELEGRAM_HIVE_OVERRIDES.
|
|
21
|
+
|
|
3
22
|
## 1.35.12
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -149,7 +149,7 @@ export const formatLogForIssue = async (logContent, logFilePath) => {
|
|
|
149
149
|
* @returns {Promise<string|null>} Issue URL or null on failure
|
|
150
150
|
*/
|
|
151
151
|
export const createIssueForError = async options => {
|
|
152
|
-
const { error, errorType, logFile, context = {} } = options;
|
|
152
|
+
const { error, errorType, logFile, context = {}, autoReport = false } = options;
|
|
153
153
|
|
|
154
154
|
try {
|
|
155
155
|
const currentUser = await getCurrentGitHubUser();
|
|
@@ -159,7 +159,20 @@ export const createIssueForError = async options => {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const errorMessage = cleanErrorMessage(error);
|
|
162
|
-
|
|
162
|
+
|
|
163
|
+
let shouldCreateIssue;
|
|
164
|
+
if (autoReport) {
|
|
165
|
+
// Auto-report mode: skip prompt, automatically create issue
|
|
166
|
+
console.log('\n❌ An error occurred:');
|
|
167
|
+
console.log(` ${errorMessage}`);
|
|
168
|
+
if (isSentryEnabled()) {
|
|
169
|
+
console.log('\n✅ Error reported to Sentry successfully');
|
|
170
|
+
}
|
|
171
|
+
console.log('\nℹ️ --auto-report-issue enabled: automatically creating GitHub issue...');
|
|
172
|
+
shouldCreateIssue = true;
|
|
173
|
+
} else {
|
|
174
|
+
shouldCreateIssue = await promptUserForIssueCreation(errorMessage);
|
|
175
|
+
}
|
|
163
176
|
|
|
164
177
|
if (!shouldCreateIssue) {
|
|
165
178
|
await log('ℹ️ Issue creation cancelled by user');
|
|
@@ -199,6 +212,20 @@ export const createIssueForError = async options => {
|
|
|
199
212
|
}
|
|
200
213
|
}
|
|
201
214
|
|
|
215
|
+
if (autoReport) {
|
|
216
|
+
issueBody += '### Instructions\n\n';
|
|
217
|
+
issueBody += 'We need to download all logs and data related about the issue to this repository, ';
|
|
218
|
+
issueBody += 'make sure we compile that data to `./docs/case-studies/issue-{id}` folder, ';
|
|
219
|
+
issueBody += 'and use it to do deep case study analysis (also make sure to search online for additional facts and data), ';
|
|
220
|
+
issueBody += 'in which we will reconstruct timeline/sequence of events, find root causes of the problem, ';
|
|
221
|
+
issueBody += 'and propose possible solutions (including known existing components/libraries, ';
|
|
222
|
+
issueBody += 'that solve similar problem or can help in solutions).\n\n';
|
|
223
|
+
issueBody += 'If there is not enough data to find actual root cause, add debug output and verbose mode if not present, ';
|
|
224
|
+
issueBody += 'that will allow us to find root cause on next iteration.\n\n';
|
|
225
|
+
issueBody += 'If issue related to any other repository/project, where we can report issues on GitHub, please do so. ';
|
|
226
|
+
issueBody += 'Each issue must contain reproducible examples, workarounds and suggestions for fix the issue in code.\n\n';
|
|
227
|
+
}
|
|
228
|
+
|
|
202
229
|
issueBody += '---\n';
|
|
203
230
|
issueBody += `*This issue was automatically created by @${currentUser} using hive-mind error reporting*\n`;
|
|
204
231
|
|
|
@@ -239,7 +266,24 @@ export const createIssueForError = async options => {
|
|
|
239
266
|
* @returns {Promise<string|null>} Issue URL if created, null otherwise
|
|
240
267
|
*/
|
|
241
268
|
export const handleErrorWithIssueCreation = async options => {
|
|
242
|
-
const { error, errorType, logFile, context = {}, skipPrompt = false } = options;
|
|
269
|
+
const { error, errorType, logFile, context = {}, skipPrompt = false, autoReport = false, disableReport = false } = options;
|
|
270
|
+
|
|
271
|
+
// --disable-report-issue takes highest precedence
|
|
272
|
+
if (disableReport) {
|
|
273
|
+
await log('ℹ️ Issue reporting disabled via --disable-report-issue.');
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// --auto-report-issue: create issue automatically without prompting
|
|
278
|
+
if (autoReport) {
|
|
279
|
+
return await createIssueForError({
|
|
280
|
+
error,
|
|
281
|
+
errorType,
|
|
282
|
+
logFile: logFile || (await getAbsoluteLogPath()),
|
|
283
|
+
context,
|
|
284
|
+
autoReport: true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
243
287
|
|
|
244
288
|
if (skipPrompt) {
|
|
245
289
|
return null;
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -1286,6 +1286,170 @@ export async function getActiveRepoWorkflows(owner, repo, verbose = false) {
|
|
|
1286
1286
|
}
|
|
1287
1287
|
}
|
|
1288
1288
|
|
|
1289
|
+
/**
|
|
1290
|
+
* Get the committed date of a specific commit from GitHub API
|
|
1291
|
+
* Issue #1480: Used to determine how recently a commit was pushed, to distinguish between
|
|
1292
|
+
* "CI not yet registered in API" (race condition) and "CI definitively not triggered"
|
|
1293
|
+
* @param {string} owner - Repository owner
|
|
1294
|
+
* @param {string} repo - Repository name
|
|
1295
|
+
* @param {string} sha - Commit SHA
|
|
1296
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
1297
|
+
* @returns {Promise<{date: Date|null, ageSeconds: number|null}>}
|
|
1298
|
+
*/
|
|
1299
|
+
export async function getCommitDate(owner, repo, sha, verbose = false) {
|
|
1300
|
+
try {
|
|
1301
|
+
const { stdout } = await exec(`gh api repos/${owner}/${repo}/commits/${sha} --jq '.commit.committer.date'`);
|
|
1302
|
+
const dateStr = stdout.trim();
|
|
1303
|
+
if (!dateStr) {
|
|
1304
|
+
return { date: null, ageSeconds: null };
|
|
1305
|
+
}
|
|
1306
|
+
const commitDate = new Date(dateStr);
|
|
1307
|
+
const ageSeconds = Math.floor((Date.now() - commitDate.getTime()) / 1000);
|
|
1308
|
+
if (verbose) {
|
|
1309
|
+
console.log(`[VERBOSE] /merge: Commit ${sha.substring(0, 7)} date: ${dateStr} (${ageSeconds}s ago)`);
|
|
1310
|
+
}
|
|
1311
|
+
return { date: commitDate, ageSeconds };
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
if (verbose) {
|
|
1314
|
+
console.log(`[VERBOSE] /merge: Error fetching commit date for ${sha}: ${error.message}`);
|
|
1315
|
+
}
|
|
1316
|
+
return { date: null, ageSeconds: null };
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Check if any previous commits in a PR had workflow runs triggered.
|
|
1322
|
+
* Issue #1480: If earlier commits in the same PR triggered CI, we should expect CI
|
|
1323
|
+
* for the HEAD commit too (unless conditions changed). This provides an additional
|
|
1324
|
+
* signal that CI should be expected and avoids false "CI not triggered" conclusions.
|
|
1325
|
+
* @param {string} owner - Repository owner
|
|
1326
|
+
* @param {string} repo - Repository name
|
|
1327
|
+
* @param {number} prNumber - Pull request number
|
|
1328
|
+
* @param {string} headSha - Current HEAD SHA (to exclude from check)
|
|
1329
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
1330
|
+
* @returns {Promise<{hadPreviousCI: boolean, previousCommitsWithCI: number, totalPreviousCommits: number}>}
|
|
1331
|
+
*/
|
|
1332
|
+
export async function checkPreviousPRCommitsHadCI(owner, repo, prNumber, headSha, verbose = false) {
|
|
1333
|
+
try {
|
|
1334
|
+
// Get all commits in the PR
|
|
1335
|
+
const { stdout: commitsJson } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits?per_page=100" --jq '[.[].sha]'`);
|
|
1336
|
+
const allShas = JSON.parse(commitsJson.trim() || '[]');
|
|
1337
|
+
|
|
1338
|
+
// Exclude the current HEAD SHA
|
|
1339
|
+
const previousShas = allShas.filter(sha => sha !== headSha);
|
|
1340
|
+
|
|
1341
|
+
if (previousShas.length === 0) {
|
|
1342
|
+
if (verbose) {
|
|
1343
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} has no previous commits to check for CI history`);
|
|
1344
|
+
}
|
|
1345
|
+
return { hadPreviousCI: false, previousCommitsWithCI: 0, totalPreviousCommits: 0 };
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Check the most recent previous commits (limit to last 3 to avoid excessive API calls)
|
|
1349
|
+
const commitsToCheck = previousShas.slice(-3);
|
|
1350
|
+
let commitsWithCI = 0;
|
|
1351
|
+
|
|
1352
|
+
for (const sha of commitsToCheck) {
|
|
1353
|
+
try {
|
|
1354
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${sha}&per_page=1" --jq '.total_count'`);
|
|
1355
|
+
const count = parseInt(stdout.trim(), 10);
|
|
1356
|
+
if (count > 0) {
|
|
1357
|
+
commitsWithCI++;
|
|
1358
|
+
}
|
|
1359
|
+
} catch {
|
|
1360
|
+
// Skip errors for individual commits
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const hadPreviousCI = commitsWithCI > 0;
|
|
1365
|
+
|
|
1366
|
+
if (verbose) {
|
|
1367
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} previous CI history: ${commitsWithCI}/${commitsToCheck.length} checked commits had workflow runs (total PR commits: ${allShas.length})`);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
return { hadPreviousCI, previousCommitsWithCI: commitsWithCI, totalPreviousCommits: previousShas.length };
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
if (verbose) {
|
|
1373
|
+
console.log(`[VERBOSE] /merge: Error checking previous PR commits CI history: ${error.message}`);
|
|
1374
|
+
}
|
|
1375
|
+
return { hadPreviousCI: false, previousCommitsWithCI: 0, totalPreviousCommits: 0 };
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Check if any workflow files in the repository have PR-related triggers
|
|
1381
|
+
* Issue #1480: Used as additional signal to determine if CI should run on PRs.
|
|
1382
|
+
* Parses .github/workflows/*.yml files from the repository content API.
|
|
1383
|
+
* @param {string} owner - Repository owner
|
|
1384
|
+
* @param {string} repo - Repository name
|
|
1385
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
1386
|
+
* @returns {Promise<{hasPRTriggers: boolean, hasWorkflowFiles: boolean, workflows: Array<{name: string, triggers: string[]}>}>}
|
|
1387
|
+
*/
|
|
1388
|
+
export async function checkWorkflowsHavePRTriggers(owner, repo, verbose = false) {
|
|
1389
|
+
try {
|
|
1390
|
+
// List workflow files in .github/workflows/
|
|
1391
|
+
const { stdout: listJson } = await exec(`gh api "repos/${owner}/${repo}/contents/.github/workflows" --jq '[.[] | select(.name | test("\\\\.(yml|yaml)$")) | {name: .name, download_url: .download_url, path: .path}]' 2>/dev/null`);
|
|
1392
|
+
const files = JSON.parse(listJson.trim() || '[]');
|
|
1393
|
+
|
|
1394
|
+
if (files.length === 0) {
|
|
1395
|
+
if (verbose) {
|
|
1396
|
+
console.log(`[VERBOSE] /merge: No workflow files found in ${owner}/${repo}/.github/workflows/ — no CI/CD will execute`);
|
|
1397
|
+
}
|
|
1398
|
+
// Issue #1480: hasWorkflowFiles=false is a strong signal that no CI/CD is configured at the file level
|
|
1399
|
+
return { hasPRTriggers: false, hasWorkflowFiles: false, workflows: [] };
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const prTriggerPatterns = [/\bon:\s*\n\s+pull_request/m, /\bon:\s*\[.*pull_request.*\]/m, /\bon:\s*pull_request\b/m, /\bpull_request_target\b/m];
|
|
1403
|
+
|
|
1404
|
+
// Also check for push triggers (push to PR branches triggers CI)
|
|
1405
|
+
const pushTriggerPatterns = [/\bon:\s*\n\s+push/m, /\bon:\s*\[.*push.*\]/m, /\bon:\s*push\b/m];
|
|
1406
|
+
|
|
1407
|
+
const results = [];
|
|
1408
|
+
|
|
1409
|
+
for (const file of files) {
|
|
1410
|
+
try {
|
|
1411
|
+
// Fetch file content (use raw content from the API)
|
|
1412
|
+
const { stdout: contentJson } = await exec(`gh api "repos/${owner}/${repo}/contents/${file.path}" --jq '.content'`);
|
|
1413
|
+
const content = Buffer.from(contentJson.trim().replace(/"/g, ''), 'base64').toString('utf-8');
|
|
1414
|
+
|
|
1415
|
+
const triggers = [];
|
|
1416
|
+
if (prTriggerPatterns.some(p => p.test(content))) {
|
|
1417
|
+
triggers.push('pull_request');
|
|
1418
|
+
}
|
|
1419
|
+
if (pushTriggerPatterns.some(p => p.test(content))) {
|
|
1420
|
+
triggers.push('push');
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (triggers.length > 0) {
|
|
1424
|
+
results.push({ name: file.name, triggers });
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (verbose) {
|
|
1428
|
+
console.log(`[VERBOSE] /merge: Workflow ${file.name}: triggers=[${triggers.join(', ')}]`);
|
|
1429
|
+
}
|
|
1430
|
+
} catch (fileError) {
|
|
1431
|
+
if (verbose) {
|
|
1432
|
+
console.log(`[VERBOSE] /merge: Error reading workflow file ${file.name}: ${fileError.message}`);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
const hasPRTriggers = results.length > 0;
|
|
1438
|
+
|
|
1439
|
+
if (verbose) {
|
|
1440
|
+
console.log(`[VERBOSE] /merge: ${results.length}/${files.length} workflow files have PR/push triggers`);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
return { hasPRTriggers, hasWorkflowFiles: true, workflows: results };
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
if (verbose) {
|
|
1446
|
+
console.log(`[VERBOSE] /merge: Error checking workflow PR triggers: ${error.message}`);
|
|
1447
|
+
}
|
|
1448
|
+
// On error, assume workflows might have PR triggers (safer: avoids false positives)
|
|
1449
|
+
return { hasPRTriggers: true, hasWorkflowFiles: true, workflows: [] };
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1289
1453
|
// Issue #1341: Re-export post-merge CI functions from separate module
|
|
1290
1454
|
import { waitForCommitCI, checkBranchCIHealth, getMergeCommitSha } from './github-merge-ci.lib.mjs';
|
|
1291
1455
|
export { waitForCommitCI, checkBranchCIHealth, getMergeCommitSha };
|
|
@@ -1323,6 +1487,10 @@ export default {
|
|
|
1323
1487
|
checkBranchCIHealth,
|
|
1324
1488
|
getMergeCommitSha,
|
|
1325
1489
|
getActiveRepoWorkflows,
|
|
1490
|
+
// Issue #1480: Commit date, workflow PR triggers, and previous commit CI history for race condition detection
|
|
1491
|
+
getCommitDate,
|
|
1492
|
+
checkPreviousPRCommitsHadCI,
|
|
1493
|
+
checkWorkflowsHavePRTriggers,
|
|
1326
1494
|
// Issue #1413: Use issue timeline to find genuinely linked PRs (avoids false positives from text search)
|
|
1327
1495
|
getLinkedPRsFromTimeline,
|
|
1328
1496
|
};
|
|
@@ -33,7 +33,7 @@ const { reportError } = sentryLib;
|
|
|
33
33
|
|
|
34
34
|
// Import GitHub merge functions
|
|
35
35
|
const githubMergeLib = await import('./github-merge.lib.mjs');
|
|
36
|
-
const { checkPRMergeable, checkMergePermissions, mergePullRequest, waitForCI, checkForBillingLimitError, getRepoVisibility, BILLING_LIMIT_ERROR_PATTERN, getDetailedCIStatus, rerunWorkflowRun, getWorkflowRunsForSha, getActiveRepoWorkflows } = githubMergeLib;
|
|
36
|
+
const { checkPRMergeable, checkMergePermissions, mergePullRequest, waitForCI, checkForBillingLimitError, getRepoVisibility, BILLING_LIMIT_ERROR_PATTERN, getDetailedCIStatus, rerunWorkflowRun, getWorkflowRunsForSha, getActiveRepoWorkflows, getCommitDate, checkPreviousPRCommitsHadCI, checkWorkflowsHavePRTriggers } = githubMergeLib;
|
|
37
37
|
|
|
38
38
|
// Import GitHub functions for log attachment
|
|
39
39
|
const githubLib = await import('./github.lib.mjs');
|
|
@@ -255,14 +255,86 @@ const getMergeBlockers = async (owner, repo, prNumber, verbose = false) => {
|
|
|
255
255
|
details: workflowRuns.map(r => r.name),
|
|
256
256
|
});
|
|
257
257
|
} else {
|
|
258
|
-
// No workflow runs for this SHA —
|
|
259
|
-
// Issue #
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
// No workflow runs for this SHA — but this could be a race condition!
|
|
259
|
+
// Issue #1480: GitHub Actions workflow runs take 30-120 seconds to appear in the
|
|
260
|
+
// API after a push. The previous fix (issue #1442) assumed 0 workflow runs meant
|
|
261
|
+
// "CI definitively NOT triggered", but this caused false positive "Ready to merge"
|
|
262
|
+
// when checked too soon after a push.
|
|
263
|
+
//
|
|
264
|
+
// Multi-layer defense (Issue #1480 enhanced):
|
|
265
|
+
// Layer 1: Grace period — check commit age
|
|
266
|
+
// Layer 2: Workflow file parsing — check .github/workflows for PR triggers
|
|
267
|
+
// Layer 3: Previous commit CI history — check if earlier PR commits had CI runs
|
|
268
|
+
const WORKFLOW_RUN_GRACE_PERIOD_SECONDS = 120; // 2 minutes — generous to cover slow GitHub API registration
|
|
269
|
+
const commitInfo = await getCommitDate(owner, repo, ciStatus.sha, verbose);
|
|
270
|
+
|
|
271
|
+
// Issue #1480: Parse workflow files for PR triggers (used in both grace period and post-grace checks)
|
|
272
|
+
const prTriggers = await checkWorkflowsHavePRTriggers(owner, repo, verbose);
|
|
273
|
+
|
|
274
|
+
// Issue #1480: If .github/workflows folder doesn't exist or has no workflow files,
|
|
275
|
+
// that's a definitive signal — no CI/CD will execute, skip grace period entirely
|
|
276
|
+
if (!prTriggers.hasWorkflowFiles) {
|
|
277
|
+
if (verbose) {
|
|
278
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} repo has no workflow files in .github/workflows/ — CI definitively not configured at file level`);
|
|
279
|
+
}
|
|
280
|
+
return { blockers, ciStatus, noCiConfigured: false, noCiTriggered: true };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (commitInfo.ageSeconds !== null && commitInfo.ageSeconds < WORKFLOW_RUN_GRACE_PERIOD_SECONDS) {
|
|
284
|
+
// Commit is recent — workflow runs may not have appeared in the API yet
|
|
285
|
+
if (verbose) {
|
|
286
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} has no workflow runs for SHA ${ciStatus.sha.substring(0, 7)}, but commit is only ${commitInfo.ageSeconds}s old (grace period: ${WORKFLOW_RUN_GRACE_PERIOD_SECONDS}s) — treating as potential race condition`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (prTriggers.hasPRTriggers) {
|
|
290
|
+
// Workflows have PR/push triggers AND commit is recent — almost certainly a race condition
|
|
291
|
+
if (verbose) {
|
|
292
|
+
console.log(`[VERBOSE] /merge: Workflow files confirm PR/push triggers exist (${prTriggers.workflows.map(w => w.name).join(', ')}) — waiting for workflow runs to appear`);
|
|
293
|
+
}
|
|
294
|
+
blockers.push({
|
|
295
|
+
type: 'ci_pending',
|
|
296
|
+
message: `CI/CD workflow runs have not appeared yet — commit is ${commitInfo.ageSeconds}s old, waiting for GitHub to register workflow runs (grace period: ${WORKFLOW_RUN_GRACE_PERIOD_SECONDS}s)`,
|
|
297
|
+
details: prTriggers.workflows.map(w => w.name),
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
// No PR triggers found in workflow files — but commit is still recent, be safe and wait
|
|
301
|
+
if (verbose) {
|
|
302
|
+
console.log(`[VERBOSE] /merge: No PR/push triggers found in workflow files, but commit is only ${commitInfo.ageSeconds}s old — waiting to be safe`);
|
|
303
|
+
}
|
|
304
|
+
blockers.push({
|
|
305
|
+
type: 'ci_pending',
|
|
306
|
+
message: `CI/CD workflow runs have not appeared yet — commit is ${commitInfo.ageSeconds}s old, waiting for GitHub to register workflow runs (grace period: ${WORKFLOW_RUN_GRACE_PERIOD_SECONDS}s)`,
|
|
307
|
+
details: [],
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
// Commit is old enough (grace period elapsed) — but check additional signals before concluding
|
|
312
|
+
// Issue #1480: Layer 3 — Check if previous commits in this PR had CI runs.
|
|
313
|
+
// If earlier commits had CI, the HEAD commit should also have CI unless conditions changed.
|
|
314
|
+
const previousCI = await checkPreviousPRCommitsHadCI(owner, repo, prNumber, ciStatus.sha, verbose);
|
|
315
|
+
|
|
316
|
+
if (previousCI.hadPreviousCI && prTriggers.hasPRTriggers) {
|
|
317
|
+
// Previous commits had CI AND workflow files have PR triggers — something is wrong,
|
|
318
|
+
// this could be a GitHub API glitch or delayed registration beyond the grace period.
|
|
319
|
+
// Wait one more cycle to be safe.
|
|
320
|
+
if (verbose) {
|
|
321
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} previous commits had CI (${previousCI.previousCommitsWithCI}/${previousCI.totalPreviousCommits}) and workflows have PR triggers, but HEAD has no runs — waiting as safety measure`);
|
|
322
|
+
}
|
|
323
|
+
blockers.push({
|
|
324
|
+
type: 'ci_pending',
|
|
325
|
+
message: `CI/CD workflow runs missing for HEAD — previous PR commits had CI (${previousCI.previousCommitsWithCI} of ${previousCI.totalPreviousCommits}), workflows have PR triggers, possible API delay`,
|
|
326
|
+
details: prTriggers.workflows.map(w => w.name),
|
|
327
|
+
});
|
|
328
|
+
} else {
|
|
329
|
+
// CI was definitively NOT triggered
|
|
330
|
+
// Issue #1442: Fork PRs needing maintainer approval, paths-ignore filtering,
|
|
331
|
+
// workflow conditions not matching, etc. all result in zero workflow runs.
|
|
332
|
+
if (verbose) {
|
|
333
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} has no CI checks and no workflow runs for SHA ${ciStatus.sha.substring(0, 7)} (commit age: ${commitInfo.ageSeconds ?? 'unknown'}s, grace period: ${WORKFLOW_RUN_GRACE_PERIOD_SECONDS}s elapsed, previous CI: ${previousCI.hadPreviousCI}, PR triggers: ${prTriggers.hasPRTriggers}) — CI was not triggered`);
|
|
334
|
+
}
|
|
335
|
+
return { blockers, ciStatus, noCiConfigured: false, noCiTriggered: true };
|
|
336
|
+
}
|
|
264
337
|
}
|
|
265
|
-
return { blockers, ciStatus, noCiConfigured: false, noCiTriggered: true };
|
|
266
338
|
}
|
|
267
339
|
} else {
|
|
268
340
|
// Repo has NO workflows — this is truly "no CI configured"
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -369,6 +369,16 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
369
369
|
description: 'Automatically initialize empty repositories by creating a simple README.md file. Only works when you have write access to the repository. This allows branch creation and pull request workflows to proceed on repositories that have no commits.',
|
|
370
370
|
default: false,
|
|
371
371
|
},
|
|
372
|
+
'auto-report-issue': {
|
|
373
|
+
type: 'boolean',
|
|
374
|
+
description: 'Automatically create a GitHub issue on failure without prompting (non-interactive mode). The issue includes error details, logs, and case study analysis instructions. Sets issue type and label to bug.',
|
|
375
|
+
default: false,
|
|
376
|
+
},
|
|
377
|
+
'disable-report-issue': {
|
|
378
|
+
type: 'boolean',
|
|
379
|
+
description: 'Disable error issue creation entirely (no prompt, no automatic creation). Overrides --auto-report-issue if both are specified.',
|
|
380
|
+
default: false,
|
|
381
|
+
},
|
|
372
382
|
'attach-solution-summary': {
|
|
373
383
|
type: 'boolean',
|
|
374
384
|
description: 'Attach the AI solution summary (from the result field) as a comment to the PR/issue after completion. The summary is extracted from the AI tool JSON output and posted under a "Solution summary" header.',
|
|
@@ -30,6 +30,8 @@ export const handleFailure = async options => {
|
|
|
30
30
|
errorType,
|
|
31
31
|
},
|
|
32
32
|
skipPrompt: !process.stdin.isTTY || argv.noIssueCreation,
|
|
33
|
+
autoReport: argv.autoReportIssue,
|
|
34
|
+
disableReport: argv.disableReportIssue,
|
|
33
35
|
});
|
|
34
36
|
} catch (issueError) {
|
|
35
37
|
reportError(issueError, {
|