maxpool 1.5.51 → 1.5.52

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.5.51",
3
+ "version": "1.5.52",
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
@@ -60,6 +60,13 @@ const DEFAULT_QUEUE = {
60
60
  maxWaitMs: 24 * 60 * 60 * 1000,
61
61
  autoMaxWaitMs: null,
62
62
  capacityMaxWaitMs: 15 * 60 * 1000,
63
+ // NETWORK-cause hold ceiling. A quota/capacity hold is worth waiting out — a reset is
64
+ // genuinely coming. A NETWORK hold is not: the connection is dead, and holding it just
65
+ // parks the caller on a socket nothing is watching. On 2026-07-28 a request was held
66
+ // 10,445s (2h54m) having produced ZERO bytes; nothing aborted it until the user touched
67
+ // the keyboard. Give up quickly instead and return a retryable 429, so the client
68
+ // reconnects on a FRESH connection — which is what actually self-heals a dead route.
69
+ networkMaxWaitMs: 2 * 60 * 1000,
63
70
  weeklyMaxWaitMs: 24 * 60 * 60 * 1000, // legacy bound; streaming holds use streamHoldMaxMs
64
71
  // STREAMING hold ceiling: how long a streaming request may be HELD ALIVE on
65
72
  // the SSE heartbeat waiting for any account to free up. Defaults to 7d (the
@@ -1387,7 +1394,7 @@ function formatRetryDuration(seconds) {
1387
1394
  function computeQueueWindowMs({
1388
1395
  cause, stream, retryPlanCause,
1389
1396
  maxWaitMs, capacityMaxWaitMs, nonStreamMaxWaitMs, streamHoldMaxMs, streamClientToleranceMs,
1390
- isCountTokens, countTokensMaxWaitMs,
1397
+ isCountTokens, countTokensMaxWaitMs, networkMaxWaitMs,
1391
1398
  }) {
1392
1399
  let windowMs;
1393
1400
  if (!stream) {
@@ -1408,6 +1415,11 @@ function computeQueueWindowMs({
1408
1415
  // the upstream processing once acquired) so a non-heartbeated metadata call fast-
1409
1416
  // fails with a retryable 429 instead of hanging past the client's idle window.
1410
1417
  if (isCountTokens && countTokensMaxWaitMs != null) windowMs = Math.min(windowMs, countTokensMaxWaitMs);
1418
+ // A dead ROUTE is not a scheduled reset: holding it out is waiting for nothing. Cap it
1419
+ // hard, independent of how patient the client is — a raised CLAUDE_STREAM_IDLE_TIMEOUT_MS
1420
+ // (the `cc` alias sets 3h) otherwise licenses a multi-hour hold on a connection that is
1421
+ // simply gone. Error-fast + client reconnect beats an unattended hold.
1422
+ if (cause === 'network' && networkMaxWaitMs != null) windowMs = Math.min(windowMs, networkMaxWaitMs);
1411
1423
  return windowMs;
1412
1424
  }
1413
1425
 
@@ -1950,6 +1962,9 @@ async function queueAndRetry(
1950
1962
  const streamClientToleranceMs = queueConfig.streamClientToleranceMs == null
1951
1963
  ? 3 * 60 * 60 * 1000
1952
1964
  : Math.max(0, Number(queueConfig.streamClientToleranceMs) || 0);
1965
+ const networkMaxWaitMs = queueConfig.networkMaxWaitMs == null
1966
+ ? 2 * 60 * 1000
1967
+ : Math.max(0, Number(queueConfig.networkMaxWaitMs) || 0);
1953
1968
  const queueWindowMs = computeQueueWindowMs({
1954
1969
  cause,
1955
1970
  stream: Boolean(requestInfo.stream),
@@ -1957,6 +1972,7 @@ async function queueAndRetry(
1957
1972
  maxWaitMs,
1958
1973
  capacityMaxWaitMs,
1959
1974
  nonStreamMaxWaitMs,
1975
+ networkMaxWaitMs,
1960
1976
  streamHoldMaxMs,
1961
1977
  streamClientToleranceMs,
1962
1978
  isCountTokens: Boolean(requestInfo.isCountTokens),
@@ -11,7 +11,12 @@
11
11
  import { spawn as realSpawn } from 'node:child_process';
12
12
 
13
13
  const POLL_MS = 5_000; // re-check the in-flight signal (idle-sleep timers are minutes)
14
- const GRACE_MS = 60_000; // stay awake this long after the last request, to bridge gaps
14
+ // Stay awake this long after the last request. 60s lost a measured race on 2026-07-28: a
15
+ // network outage drained all in-flight work, the guard released exactly 74s later, and
16
+ // macOS (idle-sleep timer) slept in the SAME SECOND — so the machine went down while the
17
+ // outage was still in progress and recovery had to wait for a keystroke. 5 minutes bridges
18
+ // an outage-shaped gap without meaningfully delaying a genuinely idle sleep.
19
+ const GRACE_MS = 300_000;
15
20
 
16
21
  export class SleepGuard {
17
22
  constructor({ getWorkPending, log = () => {}, spawn = realSpawn, enabled, pollMs = POLL_MS, graceMs = GRACE_MS } = {}) {