@link-assistant/hive-mind 1.35.4 → 1.35.6
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 +17 -0
- package/package.json +1 -1
- package/src/{github-issue-creator.lib.mjs → github-error-reporter.lib.mjs} +15 -2
- package/src/interactive-mode.lib.mjs +83 -22
- package/src/solve.auto-pr.lib.mjs +31 -82
- package/src/solve.error-handlers.lib.mjs +40 -30
- package/src/solve.mjs +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.35.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4b0beaf: Fix interactive mode PR comment output: use stdin for GitHub API calls to prevent shell quoting corruption, flush comment queue before tool result timeout to prevent stuck "Waiting for result..." comments, and guard against duplicate session started comments from late system.init events
|
|
8
|
+
|
|
9
|
+
## 1.35.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 37481da: fix: improve PR creation failure error messaging and log upload fallback (Issue #1462)
|
|
14
|
+
- Consolidate triple error output into a single clear error message when PR creation fails
|
|
15
|
+
- Upload failure logs to the issue as fallback when PR is not available (--attach-logs)
|
|
16
|
+
- Capture and log `gh pr create` stdout/stderr in verbose mode for root cause diagnosis
|
|
17
|
+
- Add fallback GitHub user detection via `gh auth status` when `gh api user` fails
|
|
18
|
+
- Rename `github-issue-creator.lib.mjs` to `github-error-reporter.lib.mjs` for clarity
|
|
19
|
+
|
|
3
20
|
## 1.35.4
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* GitHub error reporter - handles error reporting via GitHub issues and comments
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { createInterface } from 'readline';
|
|
@@ -52,7 +52,8 @@ const getCurrentGitHubUser = async () => {
|
|
|
52
52
|
try {
|
|
53
53
|
const result = await $`gh api user --jq .login`;
|
|
54
54
|
if (result.exitCode === 0) {
|
|
55
|
-
|
|
55
|
+
const user = result.stdout.toString().trim();
|
|
56
|
+
if (user) return user;
|
|
56
57
|
}
|
|
57
58
|
} catch (error) {
|
|
58
59
|
reportError(error, {
|
|
@@ -60,6 +61,18 @@ const getCurrentGitHubUser = async () => {
|
|
|
60
61
|
operation: 'gh_api_user',
|
|
61
62
|
});
|
|
62
63
|
}
|
|
64
|
+
// Issue #1462: Fallback to gh auth status when gh api user fails
|
|
65
|
+
// This handles OAuth tokens (gho_****) that may lack the 'user' API scope
|
|
66
|
+
try {
|
|
67
|
+
const authResult = await $`gh auth status --hostname github.com 2>&1`;
|
|
68
|
+
const output = (authResult.stdout?.toString() || '') + (authResult.stderr?.toString() || '');
|
|
69
|
+
const userMatch = output.match(/Logged in to github\.com account (\S+)/i) || output.match(/Logged in to github\.com as (\S+)/i);
|
|
70
|
+
if (userMatch) {
|
|
71
|
+
return userMatch[1];
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Silently ignore - will return null below
|
|
75
|
+
}
|
|
63
76
|
return null;
|
|
64
77
|
};
|
|
65
78
|
|
|
@@ -52,6 +52,13 @@ const CONFIG = {
|
|
|
52
52
|
// See: https://github.com/link-assistant/hive-mind/issues/1324
|
|
53
53
|
import { sanitizeUnicode } from './unicode-sanitization.lib.mjs';
|
|
54
54
|
|
|
55
|
+
// Use child_process for stdin-based API calls to avoid shell quoting issues
|
|
56
|
+
// with large/complex comment bodies containing backticks, quotes, etc.
|
|
57
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
58
|
+
import { execFile } from 'node:child_process';
|
|
59
|
+
import { promisify } from 'node:util';
|
|
60
|
+
const execFileAsync = promisify(execFile);
|
|
61
|
+
|
|
55
62
|
/**
|
|
56
63
|
* Truncate content in the middle, keeping start and end
|
|
57
64
|
* This helps show context while reducing size for large outputs
|
|
@@ -237,7 +244,9 @@ const getToolIcon = toolName => {
|
|
|
237
244
|
* @returns {Object} Handler object with event processing methods
|
|
238
245
|
*/
|
|
239
246
|
export const createInteractiveHandler = options => {
|
|
240
|
-
const { owner, repo, prNumber,
|
|
247
|
+
const { owner, repo, prNumber, log, verbose = false, execFile: execFileFn } = options;
|
|
248
|
+
// Use injected execFile for testability, or the real one by default
|
|
249
|
+
const runGhApi = execFileFn || execFileAsync;
|
|
241
250
|
|
|
242
251
|
// State tracking for the handler
|
|
243
252
|
const state = {
|
|
@@ -291,24 +300,35 @@ export const createInteractiveHandler = options => {
|
|
|
291
300
|
}
|
|
292
301
|
|
|
293
302
|
try {
|
|
294
|
-
// Post comment
|
|
295
|
-
|
|
303
|
+
// Post comment via gh api with stdin to avoid shell quoting issues
|
|
304
|
+
// with complex markdown bodies containing backticks, quotes, etc.
|
|
305
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
306
|
+
const apiUrl = `repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
307
|
+
const jsonPayload = JSON.stringify({ body });
|
|
308
|
+
const { stdout } = await runGhApi('gh', ['api', apiUrl, '-X', 'POST', '--input', '-'], {
|
|
309
|
+
input: jsonPayload,
|
|
310
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
311
|
+
});
|
|
296
312
|
state.lastCommentTime = Date.now();
|
|
297
313
|
|
|
298
|
-
// Extract comment ID from the
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
// Extract comment ID from the API response JSON
|
|
315
|
+
let commentId = null;
|
|
316
|
+
try {
|
|
317
|
+
const response = JSON.parse(stdout);
|
|
318
|
+
commentId = response.id ? String(response.id) : null;
|
|
319
|
+
} catch {
|
|
320
|
+
// Fallback: try to extract from URL pattern
|
|
321
|
+
const match = stdout.match(/issuecomment-(\d+)|"id":\s*(\d+)/);
|
|
322
|
+
commentId = match ? match[1] || match[2] : null;
|
|
323
|
+
}
|
|
304
324
|
|
|
305
325
|
if (verbose) {
|
|
306
|
-
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''}`, { verbose: true });
|
|
326
|
+
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''} (body: ${body.length} chars)`, { verbose: true });
|
|
307
327
|
}
|
|
308
328
|
return commentId;
|
|
309
329
|
} catch (error) {
|
|
310
330
|
if (verbose) {
|
|
311
|
-
await log(`⚠️ Interactive mode: Failed to post comment: ${error.message}`, { verbose: true });
|
|
331
|
+
await log(`⚠️ Interactive mode: Failed to post comment: ${error.message} (body: ${body.length} chars)`, { verbose: true });
|
|
312
332
|
}
|
|
313
333
|
return null;
|
|
314
334
|
}
|
|
@@ -330,14 +350,22 @@ export const createInteractiveHandler = options => {
|
|
|
330
350
|
}
|
|
331
351
|
|
|
332
352
|
try {
|
|
333
|
-
|
|
353
|
+
// Edit comment via gh api with stdin to avoid shell quoting issues
|
|
354
|
+
// with complex markdown bodies containing backticks, quotes, etc.
|
|
355
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
356
|
+
const apiUrl = `repos/${owner}/${repo}/issues/comments/${commentId}`;
|
|
357
|
+
const jsonPayload = JSON.stringify({ body });
|
|
358
|
+
await runGhApi('gh', ['api', apiUrl, '-X', 'PATCH', '--input', '-'], {
|
|
359
|
+
input: jsonPayload,
|
|
360
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
361
|
+
});
|
|
334
362
|
if (verbose) {
|
|
335
|
-
await log(`✅ Interactive mode: Comment ${commentId} updated`, { verbose: true });
|
|
363
|
+
await log(`✅ Interactive mode: Comment ${commentId} updated (body: ${body.length} chars, payload: ${jsonPayload.length} chars)`, { verbose: true });
|
|
336
364
|
}
|
|
337
365
|
return true;
|
|
338
366
|
} catch (error) {
|
|
339
367
|
if (verbose) {
|
|
340
|
-
await log(`⚠️ Interactive mode: Failed to edit comment: ${error.message}`, { verbose: true });
|
|
368
|
+
await log(`⚠️ Interactive mode: Failed to edit comment ${commentId}: ${error.message} (body: ${body.length} chars)`, { verbose: true });
|
|
341
369
|
}
|
|
342
370
|
return false;
|
|
343
371
|
}
|
|
@@ -398,6 +426,16 @@ export const createInteractiveHandler = options => {
|
|
|
398
426
|
* @param {Object} data - Event data
|
|
399
427
|
*/
|
|
400
428
|
const handleSystemInit = async data => {
|
|
429
|
+
// Guard against duplicate init events (e.g., when a late task_notification
|
|
430
|
+
// arrives after the result event and triggers a new conversation turn)
|
|
431
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
432
|
+
if (state.sessionId) {
|
|
433
|
+
if (verbose) {
|
|
434
|
+
await log(`⚠️ Interactive mode: Ignoring duplicate system.init event (session already initialized: ${state.sessionId})`, { verbose: true });
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
401
439
|
state.sessionId = data.session_id;
|
|
402
440
|
state.startTime = Date.now();
|
|
403
441
|
|
|
@@ -672,21 +710,44 @@ ${createRawJsonSection(data)}`;
|
|
|
672
710
|
// If comment ID is not yet available (comment was queued), wait for it
|
|
673
711
|
// But use a timeout to avoid blocking forever
|
|
674
712
|
if (!commentId && commentIdPromise) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
713
|
+
// First, try to flush the queue — the tool_use comment may still be
|
|
714
|
+
// waiting for rate-limit clearance. Processing it here avoids the 30s
|
|
715
|
+
// timeout that previously caused many comments to stay stuck on
|
|
716
|
+
// "Waiting for result...".
|
|
717
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
718
|
+
if (state.commentQueue.length > 0) {
|
|
719
|
+
if (verbose) {
|
|
720
|
+
await log(`🔄 Interactive mode: Flushing comment queue (${state.commentQueue.length} items) before waiting for tool use comment`, {
|
|
721
|
+
verbose: true,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
// Temporarily reset isProcessing to allow processQueue to run
|
|
725
|
+
const wasProcessing = state.isProcessing;
|
|
726
|
+
state.isProcessing = false;
|
|
727
|
+
await processQueue();
|
|
728
|
+
state.isProcessing = wasProcessing;
|
|
679
729
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
commentId =
|
|
730
|
+
|
|
731
|
+
// Check again after queue flush
|
|
732
|
+
commentId = pendingCall.commentId;
|
|
683
733
|
|
|
684
734
|
if (!commentId) {
|
|
685
735
|
if (verbose) {
|
|
686
|
-
await log(
|
|
736
|
+
await log(`⏳ Interactive mode: Waiting for tool use comment to be posted (tool: ${toolUseId})`, {
|
|
687
737
|
verbose: true,
|
|
688
738
|
});
|
|
689
739
|
}
|
|
740
|
+
// Wait for the comment to be posted (with 30 second timeout)
|
|
741
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 30000));
|
|
742
|
+
commentId = await Promise.race([commentIdPromise, timeoutPromise]);
|
|
743
|
+
|
|
744
|
+
if (!commentId) {
|
|
745
|
+
if (verbose) {
|
|
746
|
+
await log('⚠️ Interactive mode: Timeout waiting for tool use comment, posting result separately', {
|
|
747
|
+
verbose: true,
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
690
751
|
}
|
|
691
752
|
}
|
|
692
753
|
|
|
@@ -1113,12 +1113,14 @@ ${prBody}`,
|
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
1115
|
let output;
|
|
1116
|
+
let prCreateStderr = '';
|
|
1116
1117
|
let assigneeFailed = false;
|
|
1117
1118
|
|
|
1118
1119
|
// Try to create PR with assignee first (if specified)
|
|
1119
1120
|
try {
|
|
1120
1121
|
const result = await execAsync(command, { encoding: 'utf8', cwd: tempDir, env: process.env });
|
|
1121
1122
|
output = result.stdout;
|
|
1123
|
+
prCreateStderr = result.stderr || '';
|
|
1122
1124
|
} catch (firstError) {
|
|
1123
1125
|
// Check if the error is specifically about assignee validation
|
|
1124
1126
|
const errorMsg = firstError.message || '';
|
|
@@ -1146,6 +1148,7 @@ ${prBody}`,
|
|
|
1146
1148
|
// Retry without assignee - if this fails, let the error propagate to outer catch
|
|
1147
1149
|
const retryResult = await execAsync(command, { encoding: 'utf8', cwd: tempDir, env: process.env });
|
|
1148
1150
|
output = retryResult.stdout;
|
|
1151
|
+
prCreateStderr = retryResult.stderr || '';
|
|
1149
1152
|
} else {
|
|
1150
1153
|
// Not an assignee error, re-throw the original error
|
|
1151
1154
|
throw firstError;
|
|
@@ -1168,6 +1171,14 @@ ${prBody}`,
|
|
|
1168
1171
|
});
|
|
1169
1172
|
});
|
|
1170
1173
|
|
|
1174
|
+
// Log gh pr create output for debugging (Issue #1462)
|
|
1175
|
+
if (argv.verbose) {
|
|
1176
|
+
await log(` gh pr create stdout: ${(output || '').trim() || '(empty)'}`, { verbose: true });
|
|
1177
|
+
if (prCreateStderr) {
|
|
1178
|
+
await log(` gh pr create stderr: ${prCreateStderr.trim()}`, { verbose: true });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1171
1182
|
// Extract PR URL from output - gh pr create outputs the URL to stdout
|
|
1172
1183
|
prUrl = output.trim();
|
|
1173
1184
|
|
|
@@ -1214,22 +1225,11 @@ ${prBody}`,
|
|
|
1214
1225
|
}
|
|
1215
1226
|
} else {
|
|
1216
1227
|
// PR does not exist - gh pr create must have failed silently
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
await log('');
|
|
1223
|
-
await log(' 🔧 How to fix:');
|
|
1224
|
-
await log(' 1. Check if PR exists manually:');
|
|
1225
|
-
await log(` gh pr list --repo ${owner}/${repo} --head ${branchName}`);
|
|
1226
|
-
await log(' 2. Try creating PR manually:');
|
|
1227
|
-
await log(` cd ${tempDir}`);
|
|
1228
|
-
await log(` gh pr create --draft --title "Fix issue #${issueNumber}" --body "Fixes #${issueNumber}"`);
|
|
1229
|
-
await log(' 3. Check GitHub authentication:');
|
|
1230
|
-
await log(' gh auth status');
|
|
1231
|
-
await log('');
|
|
1232
|
-
throw new Error('PR creation failed - PR does not exist on GitHub');
|
|
1228
|
+
// Issue #1462: Include gh pr create stderr for root cause diagnosis
|
|
1229
|
+
const verifyStderr = verifyResult.stderr ? verifyResult.stderr.toString().trim() : '';
|
|
1230
|
+
const stderrInfo = prCreateStderr ? ` (gh pr create stderr: ${prCreateStderr.trim()})` : '';
|
|
1231
|
+
const verifyInfo = verifyStderr ? ` (gh pr view stderr: ${verifyStderr})` : '';
|
|
1232
|
+
throw new Error(`PR verification failed - gh pr create returned URL "${prUrl}" but PR #${localPrNumber} does not exist on GitHub${stderrInfo}${verifyInfo}`);
|
|
1233
1233
|
}
|
|
1234
1234
|
// Store PR info globally for error handlers
|
|
1235
1235
|
global.createdPR = { number: localPrNumber, url: prUrl };
|
|
@@ -1374,73 +1374,22 @@ ${prBody}`,
|
|
|
1374
1374
|
branchName,
|
|
1375
1375
|
operation: 'create_pull_request',
|
|
1376
1376
|
});
|
|
1377
|
+
// Issue #1462: Don't log verbose error block here - let the outer catch
|
|
1378
|
+
// handler produce a single consolidated error message to avoid triple error output.
|
|
1379
|
+
// Extract clean error message and re-throw with context.
|
|
1377
1380
|
const errorMsg = prCreateError.message || '';
|
|
1378
|
-
|
|
1379
|
-
// Clean up the error message - extract the meaningful part
|
|
1380
1381
|
let cleanError = errorMsg;
|
|
1381
1382
|
if (errorMsg.includes('pull request create failed:')) {
|
|
1382
1383
|
cleanError = errorMsg.split('pull request create failed:')[1].trim();
|
|
1383
1384
|
} else if (errorMsg.includes('Command failed:')) {
|
|
1384
|
-
// Extract just the error part, not the full command
|
|
1385
1385
|
const lines = errorMsg.split('\n');
|
|
1386
1386
|
cleanError = lines[lines.length - 1] || errorMsg;
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
|
-
// Check for specific error types
|
|
1390
|
-
// Note: Assignee errors are now handled by automatic retry in the try block above
|
|
1391
|
-
// This catch block only handles other types of PR creation failures
|
|
1392
1389
|
if (errorMsg.includes('No commits between') || errorMsg.includes("Head sha can't be blank")) {
|
|
1393
|
-
|
|
1394
|
-
await log('');
|
|
1395
|
-
await log(formatAligned('❌', 'PR CREATION FAILED', ''), { level: 'error' });
|
|
1396
|
-
await log('');
|
|
1397
|
-
await log(' 🔍 What happened:');
|
|
1398
|
-
await log(' Cannot create PR - no commits between branches.');
|
|
1399
|
-
await log('');
|
|
1400
|
-
await log(' 📦 Error details:');
|
|
1401
|
-
for (const line of cleanError.split('\n')) {
|
|
1402
|
-
if (line.trim()) await log(` ${line.trim()}`);
|
|
1403
|
-
}
|
|
1404
|
-
await log('');
|
|
1405
|
-
await log(' 💡 Possible causes:');
|
|
1406
|
-
await log(" • The branch wasn't pushed properly");
|
|
1407
|
-
await log(" • The commit wasn't created");
|
|
1408
|
-
await log(' • GitHub sync issue');
|
|
1409
|
-
await log('');
|
|
1410
|
-
await log(' 🔧 How to fix:');
|
|
1411
|
-
await log(' 1. Verify commit exists:');
|
|
1412
|
-
await log(` cd ${tempDir} && git log --format="%h %s" -5`);
|
|
1413
|
-
await log(' 2. Push again with tracking:');
|
|
1414
|
-
await log(` cd ${tempDir} && git push -u origin ${branchName}`);
|
|
1415
|
-
await log(' 3. Create PR manually:');
|
|
1416
|
-
await log(` cd ${tempDir} && gh pr create --draft`);
|
|
1417
|
-
await log('');
|
|
1418
|
-
await log(` 📂 Working directory: ${tempDir}`);
|
|
1419
|
-
await log(` 🌿 Current branch: ${branchName}`);
|
|
1420
|
-
await log('');
|
|
1421
|
-
throw new Error('PR creation failed - no commits between branches');
|
|
1390
|
+
throw new Error(`PR creation failed - no commits between branches: ${cleanError}`);
|
|
1422
1391
|
} else {
|
|
1423
|
-
|
|
1424
|
-
await log('');
|
|
1425
|
-
await log(formatAligned('❌', 'PR CREATION FAILED', ''), { level: 'error' });
|
|
1426
|
-
await log('');
|
|
1427
|
-
await log(' 🔍 What happened:');
|
|
1428
|
-
await log(' Failed to create pull request.');
|
|
1429
|
-
await log('');
|
|
1430
|
-
await log(' 📦 Error details:');
|
|
1431
|
-
for (const line of cleanError.split('\n')) {
|
|
1432
|
-
if (line.trim()) await log(` ${line.trim()}`);
|
|
1433
|
-
}
|
|
1434
|
-
await log('');
|
|
1435
|
-
await log(' 🔧 How to fix:');
|
|
1436
|
-
await log(' 1. Try creating PR manually:');
|
|
1437
|
-
await log(` cd ${tempDir} && gh pr create --draft`);
|
|
1438
|
-
await log(' 2. Check branch status:');
|
|
1439
|
-
await log(` cd ${tempDir} && git status`);
|
|
1440
|
-
await log(' 3. Verify GitHub authentication:');
|
|
1441
|
-
await log(' gh auth status');
|
|
1442
|
-
await log('');
|
|
1443
|
-
throw new Error('PR creation failed');
|
|
1392
|
+
throw new Error(`PR creation failed: ${cleanError}`);
|
|
1444
1393
|
}
|
|
1445
1394
|
}
|
|
1446
1395
|
}
|
|
@@ -1452,23 +1401,22 @@ ${prBody}`,
|
|
|
1452
1401
|
operation: 'handle_auto_pr',
|
|
1453
1402
|
});
|
|
1454
1403
|
|
|
1455
|
-
//
|
|
1456
|
-
//
|
|
1404
|
+
// Issue #1462: Single consolidated error message for PR creation failure.
|
|
1405
|
+
// Previously this was the third of three error blocks, causing confusing output.
|
|
1406
|
+
// Now this is the ONLY error block shown for PR creation failures.
|
|
1457
1407
|
await log('');
|
|
1458
1408
|
await log(formatAligned('❌', 'FATAL ERROR:', 'PR creation failed'), { level: 'error' });
|
|
1459
1409
|
await log('');
|
|
1460
|
-
await log(' 🔍 What
|
|
1461
|
-
await log(' The solve command cannot continue without a pull request.');
|
|
1462
|
-
await log(' Auto-PR creation is enabled but failed to create the PR.');
|
|
1463
|
-
await log('');
|
|
1464
|
-
await log(' 📦 Error details:');
|
|
1410
|
+
await log(' 🔍 What happened:');
|
|
1465
1411
|
await log(` ${prError.message}`);
|
|
1466
1412
|
await log('');
|
|
1413
|
+
await log(' 💡 The solve command cannot continue without a pull request.');
|
|
1414
|
+
await log('');
|
|
1467
1415
|
await log(' 🔧 How to fix:');
|
|
1468
1416
|
await log('');
|
|
1469
1417
|
await log(' Option 1: Retry without auto-PR creation');
|
|
1470
1418
|
await log(` ./solve.mjs "${issueUrl}" --no-auto-pull-request-creation`);
|
|
1471
|
-
await log(' (
|
|
1419
|
+
await log(' (The AI agent will create the PR during the session)');
|
|
1472
1420
|
await log('');
|
|
1473
1421
|
await log(' Option 2: Create PR manually first');
|
|
1474
1422
|
await log(` cd ${tempDir}`);
|
|
@@ -1482,8 +1430,9 @@ ${prBody}`,
|
|
|
1482
1430
|
await log(' gh pr create --draft # Try manually to see detailed error');
|
|
1483
1431
|
await log('');
|
|
1484
1432
|
|
|
1485
|
-
// Re-throw the error to stop execution
|
|
1486
|
-
|
|
1433
|
+
// Re-throw the error to stop execution - use prError.message directly
|
|
1434
|
+
// to avoid "PR creation failed: PR creation failed" redundancy (Issue #1462)
|
|
1435
|
+
throw prError;
|
|
1487
1436
|
}
|
|
1488
1437
|
|
|
1489
1438
|
return { prUrl, prNumber: localPrNumber, claudeCommitHash };
|
|
@@ -8,8 +8,8 @@ import { safeExit } from './exit-handler.lib.mjs';
|
|
|
8
8
|
// Import Sentry integration
|
|
9
9
|
import { reportError } from './sentry.lib.mjs';
|
|
10
10
|
|
|
11
|
-
// Import GitHub
|
|
12
|
-
import { handleErrorWithIssueCreation } from './github-
|
|
11
|
+
// Import GitHub error reporter
|
|
12
|
+
import { handleErrorWithIssueCreation } from './github-error-reporter.lib.mjs';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Handles log attachment and PR closing on failure
|
|
@@ -40,35 +40,45 @@ export const handleFailure = async options => {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// If --attach-logs is enabled, try to attach failure logs
|
|
43
|
-
if (shouldAttachLogs && getLogFile()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
if (shouldAttachLogs && getLogFile()) {
|
|
44
|
+
// Issue #1462: Upload logs to PR if available, otherwise fall back to the issue
|
|
45
|
+
const hasPR = global.createdPR && global.createdPR.number;
|
|
46
|
+
const hasIssue = global.issueNumber;
|
|
47
|
+
const targetType = hasPR ? 'pr' : hasIssue ? 'issue' : null;
|
|
48
|
+
const targetNumber = hasPR ? global.createdPR.number : hasIssue ? global.issueNumber : null;
|
|
49
|
+
const targetLabel = hasPR ? 'Pull Request' : 'Issue';
|
|
50
|
+
|
|
51
|
+
if (targetType && targetNumber) {
|
|
52
|
+
await log(`\n📄 Attempting to attach failure logs to ${targetLabel}...`);
|
|
53
|
+
try {
|
|
54
|
+
const logUploadSuccess = await attachLogToGitHub({
|
|
55
|
+
logFile: getLogFile(),
|
|
56
|
+
targetType,
|
|
57
|
+
targetNumber,
|
|
58
|
+
owner: global.owner || owner,
|
|
59
|
+
repo: global.repo || repo,
|
|
60
|
+
$,
|
|
61
|
+
log,
|
|
62
|
+
sanitizeLogContent,
|
|
63
|
+
verbose: argv.verbose,
|
|
64
|
+
errorMessage: cleanErrorMessage(error),
|
|
65
|
+
// Issue #1225: Pass model and tool info for PR comments
|
|
66
|
+
requestedModel: argv.model,
|
|
67
|
+
tool: argv.tool || 'claude',
|
|
68
|
+
});
|
|
69
|
+
if (logUploadSuccess) {
|
|
70
|
+
await log(`📎 Failure log attached to ${targetLabel}`);
|
|
71
|
+
}
|
|
72
|
+
} catch (attachError) {
|
|
73
|
+
reportError(attachError, {
|
|
74
|
+
context: 'attach_failure_log',
|
|
75
|
+
targetType,
|
|
76
|
+
targetNumber,
|
|
77
|
+
errorType,
|
|
78
|
+
operation: `attach_log_to_${targetType}`,
|
|
79
|
+
});
|
|
80
|
+
await log(`⚠️ Could not attach failure log to ${targetLabel}: ${attachError.message}`, { level: 'warning' });
|
|
63
81
|
}
|
|
64
|
-
} catch (attachError) {
|
|
65
|
-
reportError(attachError, {
|
|
66
|
-
context: 'attach_failure_log',
|
|
67
|
-
prNumber: global.createdPR?.number,
|
|
68
|
-
errorType,
|
|
69
|
-
operation: 'attach_log_to_pr',
|
|
70
|
-
});
|
|
71
|
-
await log(`⚠️ Could not attach failure log: ${attachError.message}`, { level: 'warning' });
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
|
package/src/solve.mjs
CHANGED
|
@@ -496,6 +496,9 @@ if (isPrUrl) {
|
|
|
496
496
|
issueNumber = urlNumber;
|
|
497
497
|
await log(`📝 Issue mode: Working with issue #${issueNumber}`);
|
|
498
498
|
}
|
|
499
|
+
// Issue #1462: Store issueNumber in global so error handlers can upload logs to the issue
|
|
500
|
+
// as a fallback when PR creation fails and global.createdPR is not available
|
|
501
|
+
global.issueNumber = issueNumber;
|
|
499
502
|
const workspaceInfo = argv.enableWorkspaces ? { owner, repo, issueNumber } : null;
|
|
500
503
|
const { tempDir, workspaceTmpDir, needsClone } = await setupTempDirectory(argv, workspaceInfo);
|
|
501
504
|
cleanupContext.tempDir = tempDir;
|
|
@@ -1076,19 +1079,25 @@ try {
|
|
|
1076
1079
|
await log('');
|
|
1077
1080
|
}
|
|
1078
1081
|
|
|
1079
|
-
// If --attach-logs is enabled
|
|
1082
|
+
// If --attach-logs is enabled, attach failure logs before exiting
|
|
1080
1083
|
// Note: sessionId is not required - logs should be uploaded even if agent failed before establishing a session
|
|
1081
|
-
//
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
+
// Issue #1462: Fall back to uploading logs to the issue if PR is not available
|
|
1085
|
+
const hasPR = global.createdPR && global.createdPR.number;
|
|
1086
|
+
const hasIssue = global.issueNumber;
|
|
1087
|
+
const logTargetType = hasPR ? 'pr' : hasIssue ? 'issue' : null;
|
|
1088
|
+
const logTargetNumber = hasPR ? global.createdPR.number : hasIssue ? global.issueNumber : null;
|
|
1089
|
+
const logTargetLabel = hasPR ? 'Pull Request' : 'Issue';
|
|
1090
|
+
|
|
1091
|
+
if (shouldAttachLogs && logTargetType && logTargetNumber) {
|
|
1092
|
+
await log(`\n📄 Attaching failure logs to ${logTargetLabel}...`);
|
|
1084
1093
|
try {
|
|
1085
1094
|
// Build Claude CLI resume command
|
|
1086
1095
|
const tool = argv.tool || 'claude';
|
|
1087
1096
|
const resumeCommand = sessionId && tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
|
|
1088
1097
|
const logUploadSuccess = await attachLogToGitHub({
|
|
1089
1098
|
logFile: getLogFile(),
|
|
1090
|
-
targetType:
|
|
1091
|
-
targetNumber:
|
|
1099
|
+
targetType: logTargetType,
|
|
1100
|
+
targetNumber: logTargetNumber,
|
|
1092
1101
|
owner,
|
|
1093
1102
|
repo,
|
|
1094
1103
|
$,
|
|
@@ -1110,7 +1119,7 @@ try {
|
|
|
1110
1119
|
});
|
|
1111
1120
|
|
|
1112
1121
|
if (logUploadSuccess) {
|
|
1113
|
-
await log(
|
|
1122
|
+
await log(` ✅ Failure logs uploaded to ${logTargetLabel} successfully`);
|
|
1114
1123
|
} else {
|
|
1115
1124
|
await log(' ⚠️ Failed to upload logs', { verbose: true });
|
|
1116
1125
|
}
|