maxpool 1.24.7 → 1.24.8

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 +27 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.24.7",
3
+ "version": "1.24.8",
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
@@ -39,6 +39,9 @@ const isNetworkCode = c => Boolean(c) && NETWORK_ERROR_CODES.has(c);
39
39
  const NETWORK_SOAK_NONSTREAM_MS = Math.max(5_000,
40
40
  Number(process.env.MAXPOOL_NETWORK_SOAK_NONSTREAM_MS) || 45_000);
41
41
  const QUEUE_KEEPALIVE = 'event: ping\ndata: {}\n\n';
42
+ // Claude Code's absolute per-stream ceiling (binary-verified 2026-10-07: idle timeout =
43
+ // min(env-or-300s-floor, 1800000)). Nothing resets it; the stream dies at 30 min.
44
+ const CLI_STREAM_CEILING_MS = 25 * 60_000; // bound network holds strictly below it
42
45
  // 240s, strictly BELOW Claude Code's hard 300s stall floor. At the old 300_000 the two
43
46
  // timers were a dead heat and the client always won — maxpool's clock starts when a chunk
44
47
  // is READ from upstream, strictly before the client parses it. Result: `upstream idle
@@ -1732,12 +1735,19 @@ function computeQueueWindowMs({
1732
1735
  // (the `cc` alias sets 3h) otherwise licenses a multi-hour hold on a connection that is
1733
1736
  // simply gone. Error-fast + client reconnect beats an unattended hold.
1734
1737
  // Network holds are NOT special-cased short any more. maxpool already re-polls every ~1s
1735
- // and each retry issues a FRESH fetch, so a hold IS "keep probing, resume the moment any
1736
- // route returns" — exactly what an unattended agent needs to survive a connectivity blip.
1737
- // Failing fast at 2 minutes handed the turn to Claude Code's retry loop, which is the
1738
- // thing that loses accumulated work. Visibility is paid for by logging/TUI, not by
1739
- // truncating the wait.
1740
- if (cause === 'network' && networkMaxWaitMs != null) windowMs = Math.min(windowMs, Math.max(networkMaxWaitMs, streamClientToleranceMs || 0));
1738
+ // Network holds are NOT special-cased short any more — but they must stay under the
1739
+ // CLI's ABSOLUTE stream ceiling (30 min: Claude Code kills the stream at
1740
+ // min(idle-env, 1800000ms) regardless of bytes flowing — pings reset the idle
1741
+ // watchdog, NOT this ceiling; 49 client-abandoned 30-33-min holds measured
1742
+ // 2026-10-07 when the client's 3h idle header licensed a 3h network hold).
1743
+ // A hold past the ceiling is worse than erroring: the client that started the
1744
+ // request is already gone when capacity returns. The client's own retry loop
1745
+ // (which survives outages natively) takes over after our fast error.
1746
+ // streamClientToleranceMs may be 3h from x-maxpool-client-stream-idle-ms, so it
1747
+ // cannot bound a NETWORK hold — only a capacity/quota hold with real content.
1748
+ if (cause === 'network') {
1749
+ windowMs = Math.min(windowMs, CLI_STREAM_CEILING_MS);
1750
+ }
1741
1751
  return windowMs;
1742
1752
  }
1743
1753
 
@@ -2573,8 +2583,18 @@ async function queueAndRetry(
2573
2583
  // floor applies with nothing resetting it — we must give up well before that so the user
2574
2584
  // sees a real message instead of a client-side timeout.
2575
2585
  if (cause === 'network') {
2586
+ // The client's ABSOLUTE stream ceiling (Claude Code binary, 2026-10-07: idle timeout
2587
+ // is min(env/300s floor, 1800000ms)) kills a stream at 30 min NO MATTER WHAT bytes
2588
+ // flow — pings reset the idle watchdog, not this ceiling. Measured 2026-10-07: 49
2589
+ // client-abandoned holds clustered at 1700-2100s (30-33 min) while streamHoldMaxMs
2590
+ // licensed 7-day holds; the user saw "Waiting for API response · will retry in X"
2591
+ // across sessions with no recovery because the hold outlived every client that
2592
+ // started it. Cap network holds at 25 min: when the network is actually back the
2593
+ // hold resumes in seconds; when it is not, error-fast lets the CLI's OWN retry
2594
+ // (which survives outages and reconnects — the native behavior the owner compared
2595
+ // against) take over instead of dying on our silent 30-min ceiling.
2576
2596
  const budgetMs = requestInfo.stream
2577
- ? Math.max(60_000, Number(queueConfig.networkMaxWaitMs) || 120_000)
2597
+ ? Math.min(Math.max(60_000, Number(queueConfig.networkMaxWaitMs) || 120_000), CLI_STREAM_CEILING_MS)
2578
2598
  : NETWORK_SOAK_NONSTREAM_MS;
2579
2599
  requestInfo.networkSoakDeadline ||= Date.now() + budgetMs;
2580
2600
  if (Date.now() >= requestInfo.networkSoakDeadline) {