@link-assistant/hive-mind 1.34.3 → 1.34.4
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 +10 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +11 -11
- package/src/solve.auto-merge.lib.mjs +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.34.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c3806b5: Fix missing log upload on tool failure and make HTTP 529 overload error retryable (Issue #1439)
|
|
8
|
+
|
|
9
|
+
Two fixes:
|
|
10
|
+
1. When `--attach-logs` is enabled and the tool execution fails during an auto-restart session, the failure log was not being uploaded to GitHub. Now the log is attached before stopping on both tool execution failure paths.
|
|
11
|
+
2. HTTP 529 (Anthropic "Overloaded") errors were not recognized as transient/retryable by the outer retry loop. The code only matched `API Error: 500` + `Overloaded`, but 529 uses `API Error: 529` + `overloaded_error`. Now both 500 and 529 overload errors trigger the retry logic with exponential backoff.
|
|
12
|
+
|
|
3
13
|
## 1.34.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/package.json
CHANGED
package/src/claude.lib.mjs
CHANGED
|
@@ -129,8 +129,8 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
|
|
|
129
129
|
return null;
|
|
130
130
|
};
|
|
131
131
|
const jsonError = checkForJsonError(stdout) || checkForJsonError(stderr);
|
|
132
|
-
// Check for API overload error pattern
|
|
133
|
-
const isOverloadError = (stdout.includes('API Error: 500') && stdout.includes('Overloaded')) || (stderr.includes('API Error: 500') && stderr.includes('Overloaded')) || (jsonError && jsonError.type === 'api_error' && jsonError.message === 'Overloaded');
|
|
132
|
+
// Check for API overload error pattern (Issue #1439: also detect 529 overloaded_error)
|
|
133
|
+
const isOverloadError = (stdout.includes('API Error: 500') && stdout.includes('Overloaded')) || (stdout.includes('API Error: 529') && stdout.includes('Overloaded')) || (stderr.includes('API Error: 500') && stderr.includes('Overloaded')) || (stderr.includes('API Error: 529') && stderr.includes('Overloaded')) || (jsonError && (jsonError.type === 'api_error' || jsonError.type === 'overloaded_error') && jsonError.message === 'Overloaded');
|
|
134
134
|
// Handle overload errors with retry
|
|
135
135
|
if (isOverloadError) {
|
|
136
136
|
if (retryCount < maxRetries) {
|
|
@@ -168,7 +168,7 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
|
|
|
168
168
|
}
|
|
169
169
|
// Check for error patterns in successful response
|
|
170
170
|
if (jsonError) {
|
|
171
|
-
if (jsonError.type === 'api_error' && jsonError.message === 'Overloaded') {
|
|
171
|
+
if ((jsonError.type === 'api_error' || jsonError.type === 'overloaded_error') && jsonError.message === 'Overloaded') {
|
|
172
172
|
if (retryCount < maxRetries) {
|
|
173
173
|
const delay = baseDelay * Math.pow(2, retryCount);
|
|
174
174
|
await log(`⚠️ API overload error in response. Retrying in ${delay / 1000} seconds...`, {
|
|
@@ -193,7 +193,7 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
|
|
|
193
193
|
return true;
|
|
194
194
|
} catch (error) {
|
|
195
195
|
const errorStr = error.message || error.toString();
|
|
196
|
-
if ((errorStr.includes('API Error: 500') && errorStr.includes('Overloaded')) || (errorStr.includes('api_error') && errorStr.includes('Overloaded'))) {
|
|
196
|
+
if ((errorStr.includes('API Error: 500') && errorStr.includes('Overloaded')) || (errorStr.includes('API Error: 529') && errorStr.includes('Overloaded')) || (errorStr.includes('api_error') && errorStr.includes('Overloaded')) || (errorStr.includes('overloaded_error') && errorStr.includes('Overloaded'))) {
|
|
197
197
|
if (retryCount < maxRetries) {
|
|
198
198
|
const delay = baseDelay * Math.pow(2, retryCount);
|
|
199
199
|
await log(`⚠️ API overload error during validation. Retrying in ${delay / 1000} seconds...`, {
|
|
@@ -1067,11 +1067,11 @@ export const executeClaudeCommand = async params => {
|
|
|
1067
1067
|
const content = Array.isArray(data.message.content) ? data.message.content : [data.message.content];
|
|
1068
1068
|
for (const item of content) {
|
|
1069
1069
|
if (item.type === 'text' && item.text) {
|
|
1070
|
-
// Check for the specific 500 overload error pattern
|
|
1071
|
-
if (item.text.includes('API Error: 500') && item.text.includes('api_error') && item.text.includes('Overloaded')) {
|
|
1070
|
+
// Check for the specific 500/529 overload error pattern (Issue #1439: 529 is also an overload)
|
|
1071
|
+
if ((item.text.includes('API Error: 500') || item.text.includes('API Error: 529')) && (item.text.includes('api_error') || item.text.includes('overloaded_error')) && item.text.includes('Overloaded')) {
|
|
1072
1072
|
isOverloadError = true;
|
|
1073
1073
|
lastMessage = item.text;
|
|
1074
|
-
await log(
|
|
1074
|
+
await log(`⚠️ Detected API overload error${item.text.includes('529') ? ' (529)' : ' (500)'}`, { verbose: true });
|
|
1075
1075
|
}
|
|
1076
1076
|
if (item.text.includes('API Error: 500') && item.text.includes('Internal server error') && !item.text.includes('Overloaded')) {
|
|
1077
1077
|
isInternalServerError = true;
|
|
@@ -1194,7 +1194,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1194
1194
|
|
|
1195
1195
|
// Issues #1331, #1353: Unified handler for transient API errors (Overloaded, 503, Internal Server Error,
|
|
1196
1196
|
// Request timed out). All use exponential backoff with session preservation via --resume.
|
|
1197
|
-
const isTransientError = isOverloadError || isInternalServerError || is503Error || isRequestTimeout || (lastMessage.includes('API Error: 500') && (lastMessage.includes('Overloaded') || lastMessage.includes('Internal server error'))) || (lastMessage.includes('api_error') && lastMessage.includes('Overloaded')) || lastMessage.includes('API Error: 503') || (lastMessage.includes('503') && (lastMessage.includes('upstream connect error') || lastMessage.includes('remote connection failure'))) || lastMessage === 'Request timed out' || lastMessage.includes('Request timed out');
|
|
1197
|
+
const isTransientError = isOverloadError || isInternalServerError || is503Error || isRequestTimeout || (lastMessage.includes('API Error: 500') && (lastMessage.includes('Overloaded') || lastMessage.includes('Internal server error'))) || (lastMessage.includes('API Error: 529') && (lastMessage.includes('overloaded_error') || lastMessage.includes('Overloaded'))) || (lastMessage.includes('api_error') && lastMessage.includes('Overloaded')) || (lastMessage.includes('overloaded_error') && lastMessage.includes('Overloaded')) || lastMessage.includes('API Error: 503') || (lastMessage.includes('503') && (lastMessage.includes('upstream connect error') || lastMessage.includes('remote connection failure'))) || lastMessage === 'Request timed out' || lastMessage.includes('Request timed out');
|
|
1198
1198
|
if ((commandFailed || isTransientError) && isTransientError) {
|
|
1199
1199
|
// Issue #1353: Timeouts use longer backoff (5min–1hr) vs general transient (2min–30min)
|
|
1200
1200
|
const maxRetries = isRequestTimeout ? retryLimits.maxRequestTimeoutRetries : retryLimits.maxTransientErrorRetries;
|
|
@@ -1222,7 +1222,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1222
1222
|
}
|
|
1223
1223
|
if (retryCount < maxRetries) {
|
|
1224
1224
|
const delay = Math.min(initialDelay * Math.pow(retryLimits.retryBackoffMultiplier, retryCount), maxDelay);
|
|
1225
|
-
const errorLabel = isRequestTimeout ? 'Request timeout' : isOverloadError || (lastMessage.includes('API Error: 500') && lastMessage.includes('Overloaded'))
|
|
1225
|
+
const errorLabel = isRequestTimeout ? 'Request timeout' : isOverloadError || (lastMessage.includes('API Error: 500') && lastMessage.includes('Overloaded')) || (lastMessage.includes('API Error: 529') && lastMessage.includes('Overloaded')) ? `API overload (${lastMessage.includes('529') ? '529' : '500'})` : isInternalServerError || lastMessage.includes('Internal server error') ? 'Internal server error (500)' : '503 network error';
|
|
1226
1226
|
const notRetryableHint = apiMarkedNotRetryable ? ' (API says not retryable — will stop early if no progress)' : '';
|
|
1227
1227
|
await log(`\n⚠️ ${errorLabel} detected. Retry ${retryCount + 1}/${maxRetries} in ${Math.round(delay / 60000)} min (session preserved)${notRetryableHint}...`, { level: 'warning' });
|
|
1228
1228
|
await log(` Error: ${lastMessage.substring(0, 200)}`, { verbose: true });
|
|
@@ -1393,7 +1393,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1393
1393
|
// Issue #1353: Also handle "Request timed out" in exception block
|
|
1394
1394
|
// (Overloaded, 503, Internal Server Error, Request timed out) - all with session preservation
|
|
1395
1395
|
const isTimeoutException = errorStr === 'Request timed out' || errorStr.includes('Request timed out');
|
|
1396
|
-
const isTransientException = isTimeoutException || (errorStr.includes('API Error: 500') && (errorStr.includes('Overloaded') || errorStr.includes('Internal server error'))) || (errorStr.includes('api_error') && errorStr.includes('Overloaded')) || errorStr.includes('API Error: 503') || (errorStr.includes('503') && (errorStr.includes('upstream connect error') || errorStr.includes('remote connection failure')));
|
|
1396
|
+
const isTransientException = isTimeoutException || (errorStr.includes('API Error: 500') && (errorStr.includes('Overloaded') || errorStr.includes('Internal server error'))) || (errorStr.includes('API Error: 529') && (errorStr.includes('overloaded_error') || errorStr.includes('Overloaded'))) || (errorStr.includes('api_error') && errorStr.includes('Overloaded')) || (errorStr.includes('overloaded_error') && errorStr.includes('Overloaded')) || errorStr.includes('API Error: 503') || (errorStr.includes('503') && (errorStr.includes('upstream connect error') || errorStr.includes('remote connection failure')));
|
|
1397
1397
|
if (isTransientException) {
|
|
1398
1398
|
// Issue #1353: Use timeout-specific backoff for request timeouts
|
|
1399
1399
|
const maxRetries = isTimeoutException ? retryLimits.maxRequestTimeoutRetries : retryLimits.maxTransientErrorRetries;
|
|
@@ -1401,7 +1401,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1401
1401
|
const maxDelay = isTimeoutException ? retryLimits.maxRequestTimeoutDelayMs : retryLimits.maxTransientErrorDelayMs;
|
|
1402
1402
|
if (retryCount < maxRetries) {
|
|
1403
1403
|
const delay = Math.min(initialDelay * Math.pow(retryLimits.retryBackoffMultiplier, retryCount), maxDelay);
|
|
1404
|
-
const errorLabel = isTimeoutException ? 'Request timeout' : errorStr.includes('Overloaded') ?
|
|
1404
|
+
const errorLabel = isTimeoutException ? 'Request timeout' : errorStr.includes('Overloaded') ? `API overload (${errorStr.includes('529') ? '529' : '500'})` : errorStr.includes('Internal server error') ? 'Internal server error (500)' : '503 network error';
|
|
1405
1405
|
await log(`\n⚠️ ${errorLabel} in exception. Retry ${retryCount + 1}/${maxRetries} in ${Math.round(delay / 60000)} min (session preserved)...`, { level: 'warning' });
|
|
1406
1406
|
if (sessionId && !argv.resume) argv.resume = sessionId;
|
|
1407
1407
|
await waitWithCountdown(delay, log);
|
|
@@ -739,6 +739,40 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
739
739
|
await log('');
|
|
740
740
|
await log(formatAligned('❌', `${argv.tool.toUpperCase()} RESUME FAILED`, ''));
|
|
741
741
|
await log(formatAligned('', 'Action:', 'Stopping auto-restart — tool execution failed after limit reset', 2));
|
|
742
|
+
// Issue #1439: Attach failure log before stopping, so user can see what happened
|
|
743
|
+
const shouldAttachLogsOnResumeFail = argv.attachLogs || argv['attach-logs'];
|
|
744
|
+
if (prNumber && shouldAttachLogsOnResumeFail) {
|
|
745
|
+
try {
|
|
746
|
+
const logFile = getLogFile();
|
|
747
|
+
if (logFile) {
|
|
748
|
+
await attachLogToGitHub({
|
|
749
|
+
logFile,
|
|
750
|
+
targetType: 'pr',
|
|
751
|
+
targetNumber: prNumber,
|
|
752
|
+
owner,
|
|
753
|
+
repo,
|
|
754
|
+
$,
|
|
755
|
+
log,
|
|
756
|
+
sanitizeLogContent,
|
|
757
|
+
verbose: argv.verbose,
|
|
758
|
+
errorMessage: `${argv.tool.toUpperCase()} execution failed after limit reset`,
|
|
759
|
+
sessionId: latestSessionId,
|
|
760
|
+
tempDir,
|
|
761
|
+
requestedModel: argv.model,
|
|
762
|
+
tool: argv.tool || 'claude',
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
} catch (logUploadError) {
|
|
766
|
+
reportError(logUploadError, {
|
|
767
|
+
context: 'attach_auto_restart_failure_log',
|
|
768
|
+
prNumber,
|
|
769
|
+
owner,
|
|
770
|
+
repo,
|
|
771
|
+
operation: 'upload_failure_log',
|
|
772
|
+
});
|
|
773
|
+
await log(formatAligned('', `⚠️ Failure log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
742
776
|
return { success: false, reason: 'tool_failure_after_resume', latestSessionId, latestAnthropicCost };
|
|
743
777
|
}
|
|
744
778
|
} else {
|
|
@@ -755,6 +789,40 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
755
789
|
await log('');
|
|
756
790
|
await log(formatAligned('❌', `${argv.tool.toUpperCase()} EXECUTION FAILED`, ''));
|
|
757
791
|
await log(formatAligned('', 'Action:', 'Stopping auto-restart — tool execution failed', 2));
|
|
792
|
+
// Issue #1439: Attach failure log before stopping, so user can see what happened
|
|
793
|
+
const shouldAttachLogsOnFail = argv.attachLogs || argv['attach-logs'];
|
|
794
|
+
if (prNumber && shouldAttachLogsOnFail) {
|
|
795
|
+
try {
|
|
796
|
+
const logFile = getLogFile();
|
|
797
|
+
if (logFile) {
|
|
798
|
+
await attachLogToGitHub({
|
|
799
|
+
logFile,
|
|
800
|
+
targetType: 'pr',
|
|
801
|
+
targetNumber: prNumber,
|
|
802
|
+
owner,
|
|
803
|
+
repo,
|
|
804
|
+
$,
|
|
805
|
+
log,
|
|
806
|
+
sanitizeLogContent,
|
|
807
|
+
verbose: argv.verbose,
|
|
808
|
+
errorMessage: `${argv.tool.toUpperCase()} execution failed`,
|
|
809
|
+
sessionId: latestSessionId,
|
|
810
|
+
tempDir,
|
|
811
|
+
requestedModel: argv.model,
|
|
812
|
+
tool: argv.tool || 'claude',
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
} catch (logUploadError) {
|
|
816
|
+
reportError(logUploadError, {
|
|
817
|
+
context: 'attach_auto_restart_failure_log',
|
|
818
|
+
prNumber,
|
|
819
|
+
owner,
|
|
820
|
+
repo,
|
|
821
|
+
operation: 'upload_failure_log',
|
|
822
|
+
});
|
|
823
|
+
await log(formatAligned('', `⚠️ Failure log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
|
|
824
|
+
}
|
|
825
|
+
}
|
|
758
826
|
return { success: false, reason: 'tool_failure', latestSessionId, latestAnthropicCost };
|
|
759
827
|
} else {
|
|
760
828
|
// Success - capture latest session data
|