maxpool 1.5.73 → 1.5.75

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +37 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.5.73",
3
+ "version": "1.5.75",
4
4
  "description": "Multi-account Claude Code proxy with adaptive, rate-aware load balancing across Claude accounts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/server.js CHANGED
@@ -1043,7 +1043,18 @@ async function forwardRequest(
1043
1043
  // self-heals instead of poisoning the session into a 400 loop. Signatures are
1044
1044
  // portable in practice (verified 2026-07-02); this only fires if Anthropic
1045
1045
  // ever account-binds them.
1046
- if (lease.migratedFromName && requestInfo.sessionKey
1046
+ // ONLY for a Claude→Claude migration. When the issuer is a PROVIDER, reverting
1047
+ // there is not a repair — the provider happily serves the request, but the
1048
+ // transcript keeps its provider-authored thinking blocks, so the very next turn
1049
+ // that routes back to Claude 400s on the SAME block. Measured 2026-08-09: the
1050
+ // same coordinate (messages.5.content.23) failed 4 times in 2 minutes, each one
1051
+ // "reverting to issuer glm max@gomokka.com" and never repairing anything.
1052
+ // The correct repair for a provider-authored block is the strip below.
1053
+ const issuer = lease.migratedFromName
1054
+ ? accountManager.accounts?.find(a => a.name === lease.migratedFromName)
1055
+ : null;
1056
+ const issuerIsClaude = issuer && issuer.type !== 'provider';
1057
+ if (lease.migratedFromName && issuerIsClaude && requestInfo.sessionKey
1047
1058
  && canRetryBufferedBody && retryCount + 1 < maxAttempts && !res.headersSent) {
1048
1059
  accountManager.revertSessionBinding(requestInfo.sessionKey, lease.migratedFromName);
1049
1060
  excludedIndexes.add(account.index);
@@ -1082,8 +1093,14 @@ async function forwardRequest(
1082
1093
  // its life. Verified against the real API — the stripped history returns 200, with
1083
1094
  // text + tool_use preserved. Tried once per request (thinkingStripped guard); if it
1084
1095
  // still fails, the provider pin below is the fallback.
1085
- if (isSignatureRejection && !requestInfo.thinkingStripped
1086
- && canRetryBufferedBody && canRepairBody) {
1096
+ // NOT gated on canRetryBufferedBody. That limit exists to stop a huge body being
1097
+ // re-sent across accounts on a FAILOVER — but a repair SHRINKS the body (measured:
1098
+ // 11.5MB → 5.7MB in 20ms) and is the only thing standing between the user and a
1099
+ // dead session. The body is already fully in memory by this point, so refusing to
1100
+ // rewrite it saves nothing; it just guarantees the 400 surfaces. Reported
1101
+ // 2026-08-10: "history too large to rewrite automatically" on a session the strip
1102
+ // would have fixed in 20ms. The retry it schedules re-checks the SHRUNK size.
1103
+ if (isSignatureRejection && !requestInfo.thinkingStripped && canRepairBody) {
1087
1104
  console.log(`[Maxpool] Anthropic rejected a block: ${describeRejectedBlock(body, errorBody)}`);
1088
1105
  const { body: cleanBody, removed, converted } = stripForeignThinkingBlocks(body);
1089
1106
  if (cleanBody) {
@@ -1091,10 +1108,14 @@ async function forwardRequest(
1091
1108
  // rejected round-trip (the client resends the full poisoned history each turn).
1092
1109
  accountManager.markSessionThinkingContaminated?.(requestInfo.sessionKey);
1093
1110
  console.log(`[Maxpool] Recovering session on Claude: stripped ${removed} provider thinking block(s), converted ${converted} provider search block(s) to text`);
1111
+ // Re-derive the retry budget from the SHRUNK body. Inheriting the old flag
1112
+ // would leave a now-5.7MB body permanently marked "too big to retry" because
1113
+ // it was 11.5MB before the repair — barring the very failover the repair
1114
+ // exists to enable.
1094
1115
  return forwardRequest(
1095
1116
  req, res, cleanBody, accountManager, upstream, retryCount + 1, hooks, reqId, ctx, logDir,
1096
1117
  retryConfig, queueConfig, { ...requestInfo, thinkingStripped: true, repairCount: repairCount + 1 },
1097
- canRetryBufferedBody, canQueueBufferedBody, excludedIndexes,
1118
+ cleanBody.length <= retryConfig.maxRetryBufferBytes, canQueueBufferedBody, excludedIndexes,
1098
1119
  );
1099
1120
  }
1100
1121
  }
@@ -1102,7 +1123,9 @@ async function forwardRequest(
1102
1123
  // COORDINATE REPAIR (runs after the broad strip found nothing, or found the wrong
1103
1124
  // thing). Anthropic pointed at an exact block index; trust that over our own shape
1104
1125
  // model. Its own flag, so it still fires on a request whose broad strip already ran.
1105
- if (isSignatureRejection && !requestInfo.rejectedBlockStripped && canRetryBufferedBody && canRepairBody) {
1126
+ // Same reasoning as the broad strip above: a repair SHRINKS the body, so the
1127
+ // re-send limit must not bar it.
1128
+ if (isSignatureRejection && !requestInfo.rejectedBlockStripped && canRepairBody) {
1106
1129
  const { body: fixedBody, removed, type } = stripRejectedBlockClass(body, errorBody);
1107
1130
  if (fixedBody) {
1108
1131
  // Latch the session ONLY for the class the pre-strip can actually repair up
@@ -1122,7 +1145,7 @@ async function forwardRequest(
1122
1145
  thinkingStripped: preStripCanRepeat || requestInfo.thinkingStripped,
1123
1146
  repairCount: repairCount + 1,
1124
1147
  },
1125
- canRetryBufferedBody, canQueueBufferedBody, excludedIndexes,
1148
+ fixedBody.length <= retryConfig.maxRetryBufferBytes, canQueueBufferedBody, excludedIndexes,
1126
1149
  );
1127
1150
  }
1128
1151
  }
@@ -1187,16 +1210,14 @@ async function forwardRequest(
1187
1210
  // and without it the two surviving explanations (a block the strip cannot see
1188
1211
  // vs. a body over the retry buffer) are indistinguishable in the log.
1189
1212
  console.log(`[Maxpool] Unrepaired signature 400: ${describeRejectedBlock(body, errorBody)} bufferable=${canRetryBufferedBody} stripped=${!!requestInfo.thinkingStripped}`);
1190
- // A body over the retry buffer bars EVERY repair above without a word — the user
1191
- // then sees "could not repair" for a transcript maxpool never even tried to fix.
1192
- // Name that separately so the reason is actionable rather than mysterious.
1193
- // `peek` (not the full class-strip) because on THIS path the body may be the very
1194
- // one just declared too large to rewrite — a full parse+rebuild there costs 15ms
1195
- // and a discarded 4.8MB Buffer on a 9.6MB body, to read one string.
1196
- const rejectedType = canRetryBufferedBody ? peekRejectedBlockType(body, errorBody) : null;
1197
- const what = !canRetryBufferedBody
1198
- ? `This session's history is too large for maxpool to rewrite automatically (over ${Math.round(retryConfig.maxRetryBufferBytes / (1024 * 1024))}MB). Run /compact and it will keep going.`
1199
- : rejectedType && rejectedType !== 'thinking' && rejectedType !== 'redacted_thinking'
1213
+ // `peek` (not the full class-strip) — reads only `.type`, no parse+rebuild.
1214
+ // Read it regardless of body size: the repairs are no longer size-gated, so a
1215
+ // large body reaches here for a REAL reason (an unremovable block type) and the
1216
+ // user deserves to be told which one.
1217
+ const rejectedType = peekRejectedBlockType(body, errorBody);
1218
+ // NOTE: no "too large to rewrite" branch any more — the repairs above are no
1219
+ // longer gated on canRetryBufferedBody, so size never blocks a repair attempt.
1220
+ const what = rejectedType && rejectedType !== 'thinking' && rejectedType !== 'redacted_thinking'
1200
1221
  ? `Anthropic rejected a "${rejectedType}" block in this session's history, which maxpool cannot remove without losing conversation content.`
1201
1222
  : requestInfo.thinkingStripped || requestInfo.rejectedBlockStripped
1202
1223
  ? 'This session ran on GLM/Kimi earlier, and Anthropic will not accept parts of what they wrote. Maxpool repaired what it could and retried on Claude, but Anthropic still rejected the history.'