claude-multiacc 2.0.21 → 2.0.23

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/lib/report.py CHANGED
@@ -255,6 +255,14 @@ def _usage(d, stale_after):
255
255
  # the reading cannot be used for degraded ranking.
256
256
  if lim.get('weekly_resets_epoch'):
257
257
  out['weekly_resets_epoch'] = lim['weekly_resets_epoch']
258
+ # The writer found no INFORMATIVE bucket (every percentage 0 with no reset window)
259
+ # and refused to invent numbers from it, so the ranking fields above are all None
260
+ # here and the account ranks as unknown rather than as empty — the 2026-09-04
261
+ # incident, where fake-zero telemetry made two exhausted accounts the band leaders.
262
+ # Emitted only when set, and always as a plain true, so a consumer that has never
263
+ # seen the flag reads exactly the shape it always did.
264
+ if lim.get('no_data'):
265
+ out['no_data'] = True
258
266
  if lim.get('last_error'):
259
267
  out['last_error'] = lim['last_error']
260
268
  if lim.get('last_error_at'):
@@ -281,12 +289,34 @@ def _last_pick(root, aid):
281
289
 
282
290
 
283
291
 
292
+ def _rankable(u):
293
+ """True when this reading can still rank its account in the shim: inside the
294
+ ranking window AND carrying BOTH of the fields selection reads (bin/claude
295
+ rank_weekly_of / rank_session_of, gated by pick_best's "known" rule and by
296
+ telem_blind, which uses exactly this rule). A no_data document — all-zero buckets
297
+ with no reset window, 2026-09-04 — is current and well-formed but carries neither,
298
+ so a pool full of them is BLIND, not fresh; reporting it as fresh is how an outage
299
+ hides behind a recent timestamp. BOTH, not either: the same writer change omits
300
+ fields per signal, and a one-signal document (weekly-only or session-only) is
301
+ unknown to pick_best just as completely — an either-test called such a pool fresh
302
+ while the shim was tying every account and picking at random. bool is an int in
303
+ Python but not a number to the shim's digit parser, so `weekly_percent: true` must
304
+ read as unusable here too."""
305
+ if not u or u.get('stale'):
306
+ return False
307
+ return all(isinstance(u.get(k), (int, float)) and not isinstance(u.get(k), bool)
308
+ for k in ('weekly_percent', 'session_percent'))
309
+
310
+
284
311
  def telemetry_state(accounts, now=None):
285
312
  """Three-state verdict on how the shim is CURRENTLY ranking this pool, over the
286
313
  same candidate set bin/claude uses: the eligible accounts, or — when every one of
287
314
  them is limit-marked — the valid ones it falls back to.
288
315
 
289
- 'fresh' at least one candidate has telemetry inside the ranking window
316
+ 'fresh' at least one candidate has telemetry inside the ranking window that
317
+ the shim can actually rank on — a COMPLETE reading, weekly and session
318
+ both (see _rankable: a no_data document, or a one-signal one, is
319
+ in-window and ranks nothing, so it does not make a pool fresh)
290
320
  'degraded' none do, but EVERY candidate still has a weekly reading whose bucket
291
321
  has not reset yet, so the shim ranks on those (all-or-nothing: one
292
322
  unusable reading and the comparison is not apples-to-apples)
@@ -302,10 +332,16 @@ def telemetry_state(accounts, now=None):
302
332
  cands = [a for a in usable if a.get('status') == 'active'] or usable
303
333
  if not cands:
304
334
  return 'none'
305
- if any(a.get('usage') and not a['usage'].get('stale') for a in cands):
335
+ if any(_rankable(a.get('usage')) for a in cands):
306
336
  return 'fresh'
307
337
  for a in cands:
308
338
  u = a.get('usage') or {}
339
+ # DEGRADED means "everything is old, rank on old truths". A reading INSIDE the
340
+ # window that is merely unrankable (no_data, or one-signal) blocks degraded in
341
+ # the shim too (bin/claude assess_telemetry, codex review 2026-09-04): fresh
342
+ # emptiness is not an outage of age, and re-fetching will not improve it.
343
+ if not u.get('stale'):
344
+ return 'blind'
309
345
  horizon = u.get('weekly_resets_epoch') or 0
310
346
  pct = u.get('weekly_percent')
311
347
  # bool is an int in Python but not a number to the shim's digit parser, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-multiacc",
3
- "version": "2.0.21",
3
+ "version": "2.0.23",
4
4
  "description": "Unified Claude Code and OpenAI Codex subscription pooling with quota-aware selection.",
5
5
  "type": "module",
6
6
  "bin": {