@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.34.3",
3
+ "version": "1.34.4",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -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('⚠️ Detected API overload error', { verbose: true });
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')) ? 'API overload (500)' : isInternalServerError || lastMessage.includes('Internal server error') ? 'Internal server error (500)' : '503 network error';
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') ? 'API overload (500)' : errorStr.includes('Internal server error') ? 'Internal server error (500)' : '503 network error';
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