[BUG] Weekly usage bar exhausted in August with 30–45% LOWER token consumption than June (Max 20x)

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Aug 22, 2026

Plan: Max 20x · Client: Claude Code CLI 2.1.235 (native install), macOS · Promo: the +50% weekly-limit promotion (May–Aug 2026) was active in both periods compared below.

Summary

Weekly usage, measured from local transcripts (~/.claude/projects, deduplicated by requestId, all sessions and subagents included), was 30–45% higher in June than in August — yet June weeks ended at roughly 75–80% of the weekly bar while August weeks hit 100% mid-week.

| Week (Mon) | Requests | cache_read tokens | output tokens | Weekly bar at end of week |
|------------|---------:|------------------:|--------------:|---------------------------|
| Jun 1 | 27,839 | 4.71B | 9.8M | ~75–80% |
| Jun 29 | 25,666 | 5.14B | 7.0M | ~75–80% |
| Aug 3 | 11,954 | 3.30B | 5.7M | 100% (maxed) |
| Aug 10 | 18,662 | 3.56B | 8.7M | 100% (maxed) |

Model mix (share of input-side tokens): June was more Opus-heavy (Opus 4.8 ≈ 71% in the Jun 1 week) than August (Opus 4.8 + Opus 5 ≈ 54% in the Aug 10 week). Fable 5: 0% in the Jun 1 week, ≈12% in the Jun 29 week, ≈39% in the Aug 3 week, ≈12% in the Aug 10 week. The cleanest like-for-like pair is therefore Jun 29 vs Aug 10: same Fable share, same Sonnet range, June 38% more requests and 45% more cache_read — June did not max, August did.

Usage pattern (interactive Claude Code plus subagent orchestration, same projects, same workflow) was unchanged across the period.

Expected

Same or lower token consumption → same or lower percentage of the weekly bar.

Actual

Lower consumption → weekly bar exhausted, with no change on my side.

Weekly-bar values are as observed in /usage at the end of each week; Claude Code does not log them locally. Server-side metering for this account over the same weeks will confirm or refute the comparison.

Per-day breakdown

Same source (local transcripts, deduplicated by requestId, main sessions + subagents). Model columns are the share of input-side tokens (cache_read + cache_write + input) per model family; the remainder is Haiku/other. Days are local time (America/Los_Angeles).

| Date | Requests | cache_read | cache_write | input | output | Opus % | Sonnet % | Fable % |
|------|---------:|-----------:|------------:|------:|-------:|-------:|---------:|--------:|
| Week of Jun 1 | | | | | | | | |
| Jun 1 Mon | 3,726 | 774.8M | 17.9M | 476k | 1.4M | 58 | 41 | 0 |
| Jun 2 Tue | 4,415 | 817.5M | 23.3M | 555k | 2.0M | 71 | 29 | 0 |
| Jun 3 Wed | 2,911 | 521.0M | 13.6M | 325k | 1.4M | 91 | 9 | 0 |
| Jun 4 Thu | 4,826 | 728.8M | 31.5M | 5.9M | 1.9M | 84 | 16 | 0 |
| Jun 5 Fri | 6,740 | 1.19B | 22.1M | 1.3M | 1.4M | 78 | 22 | 0 |
| Jun 6 Sat | 3,879 | 455.4M | 15.1M | 288k | 1.2M | 46 | 53 | 0 |
| Jun 7 Sun | 1,342 | 220.4M | 10.6M | 163k | 694k | 74 | 26 | 0 |
| Week of Jun 29 | | | | | | | | |
| Jun 29 Mon | 2,709 | 549.1M | 21.7M | 697k | 1.1M | 80 | 20 | 0 |
| Jun 30 Tue | 3,400 | 527.7M | 11.5M | 631k | 838k | 71 | 29 | 0 |
| Jul 1 Wed | 3,270 | 482.7M | 20.3M | 782k | 766k | 56 | 35 | 9 |
| Jul 2 Thu | 3,878 | 966.5M | 15.0M | 342k | 740k | 30 | 63 | 7 |
| Jul 3 Fri | 6,227 | 1.26B | 25.9M | 1.1M | 1.2M | 44 | 41 | 15 |
| Jul 4 Sat | 4,695 | 940.0M | 31.9M | 455k | 1.4M | 43 | 46 | 11 |
| Jul 5 Sun | 1,487 | 411.8M | 15.6M | 325k | 964k | 30 | 18 | 52 |
| Week of Aug 3 | | | | | | | | |
| Aug 3 Mon | 1,185 | 402.5M | 12.5M | 4k | 893k | 40 | 0 | 58 |
| Aug 4 Tue | 398 | 105.8M | 3.3M | 1k | 258k | 42 | 0 | 58 |
| Aug 5 Wed | 1,680 | 435.7M | 11.5M | 19k | 994k | 54 | 12 | 33 |
| Aug 6 Thu | 3,484 | 871.3M | 32.1M | 61k | 1.4M | 42 | 34 | 23 |
| Aug 7 Fri | 3,330 | 969.2M | 20.9M | 32k | 1.2M | 41 | 12 | 47 |
| Aug 8 Sat | 1,578 | 448.6M | 16.6M | 8k | 821k | 66 | 1 | 33 |
| Aug 9 Sun | 299 | 61.9M | 3.7M | 2k | 126k | 75 | 0 | 25 |
| Week of Aug 10 | | | | | | | | |
| Aug 10 Mon | 2,356 | 654.2M | 20.3M | 21k | 1.2M | 57 | 8 | 35 |
| Aug 11 Tue | 2,804 | 563.4M | 18.7M | 23k | 1.4M | 61 | 28 | 11 |
| Aug 12 Wed | 3,785 | 563.2M | 24.5M | 28k | 1.2M | 66 | 29 | 5 |
| Aug 13 Thu | 4,806 | 739.7M | 17.8M | 69k | 1.8M | 42 | 58 | 0 |
| Aug 14 Fri | 2,772 | 488.1M | 17.0M | 52k | 1.5M | 43 | 57 | 0 |
| Aug 15 Sat | 1,735 | 402.4M | 10.8M | 27k | 1.1M | 61 | 30 | 9 |
| Aug 16 Sun | 404 | 144.1M | 5.1M | 2k | 449k | 63 | 0 | 37 |

One visible difference between the periods: uncached input tokens are ~10–100× lower in August (tens of k/day vs hundreds of k to M/day in June), i.e. prompt caching was more effective in August, not less — which should make August cheaper, not more expensive.

Measurement script

Standard library only. Reads ~/.claude/projects/**/*.jsonl, de-duplicates by requestId, prints aggregates only (no paths, session ids, or message text). The tables above were produced with:

python3 claude_usage_report.py --weeks 2026-06-01,2026-06-29,2026-08-03,2026-08-10

<details>
<summary>claude_usage_report.py</summary>

#!/usr/bin/env python3
"""Aggregate Claude Code token usage from local transcripts.

Reads ~/.claude/projects/**/*.jsonl, de-duplicates API responses by requestId,
and prints per-week and per-day totals plus model-family shares.
Output contains aggregate numbers only: no paths, session ids, or message text.

Usage:
  python3 claude_usage_report.py                 # all weeks found
  python3 claude_usage_report.py --since 2026-06-01
  python3 claude_usage_report.py --weeks 2026-06-01,2026-06-29,2026-08-03,2026-08-10
  python3 claude_usage_report.py --tz -7         # UTC offset hours for day bucketing (default: local)
"""
import argparse
import collections
import datetime as dt
import glob
import json
import os
import sys


def family(model: str) -> str:
    m = (model or "").lower()
    for f in ("opus", "sonnet", "fable", "haiku"):
        if f in m:
            return f
    return "other"


def fmt(n: float) -> str:
    if n >= 1e9:
        return f"{n / 1e9:.2f}B"
    if n >= 1e6:
        return f"{n / 1e6:.1f}M"
    if n >= 1e3:
        return f"{n / 1e3:.0f}k"
    return str(int(n))


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", default=os.path.expanduser("~/.claude/projects"))
    ap.add_argument("--since", default=None, help="YYYY-MM-DD (local)")
    ap.add_argument("--weeks", default=None, help="comma-separated Monday dates to include")
    ap.add_argument("--tz", type=float, default=None, help="UTC offset in hours; default = system local")
    args = ap.parse_args()

    if args.tz is None:
        tz = dt.datetime.now().astimezone().tzinfo
    else:
        tz = dt.timezone(dt.timedelta(hours=args.tz))
    since = dt.date.fromisoformat(args.since) if args.since else None
    weeks = set(args.weeks.split(",")) if args.weeks else None

    seen = set()
    day = collections.defaultdict(collections.Counter)
    fam_tok = collections.defaultdict(collections.Counter)
    files = 0

    for path in glob.glob(os.path.join(args.root, "**", "*.jsonl"), recursive=True):
        files += 1
        try:
            fh = open(path, errors="replace")
        except OSError:
            continue
        with fh:
            for line in fh:
                if '"usage"' not in line:
                    continue
                try:
                    rec = json.loads(line)
                except json.JSONDecodeError:
                    continue
                msg = rec.get("message") or {}
                usage = msg.get("usage") or {}
                if not usage:
                    continue
                rid = rec.get("requestId") or msg.get("id")
                if not rid or rid in seen:
                    continue
                seen.add(rid)
                ts = str(rec.get("timestamp", ""))
                try:
                    d = dt.datetime.fromisoformat(ts.replace("Z", "+00:00")).astimezone(tz)
                except ValueError:
                    continue
                dd = d.date()
                if since and dd < since:
                    continue
                wk = (dd - dt.timedelta(days=dd.weekday())).isoformat()
                if weeks and wk not in weeks:
                    continue
                c = day[dd.isoformat()]
                c["n"] += 1
                c["in"] += usage.get("input_tokens", 0) or 0
                c["out"] += usage.get("output_tokens", 0) or 0
                c["cW"] += usage.get("cache_creation_input_tokens", 0) or 0
                c["cR"] += usage.get("cache_read_input_tokens", 0) or 0
                inp_side = (usage.get("cache_read_input_tokens", 0) or 0) \
                    + (usage.get("cache_creation_input_tokens", 0) or 0) \
                    + (usage.get("input_tokens", 0) or 0)
                fam_tok[dd.isoformat()][family(msg.get("model"))] += inp_side

    if not day:
        print("no usage records found", file=sys.stderr)
        return 1

    print(f"files scanned: {files} · unique requests: {len(seen)}\n")

    # weekly
    wkagg = collections.defaultdict(collections.Counter)
    for k, c in day.items():
        d = dt.date.fromisoformat(k)
        wk = (d - dt.timedelta(days=d.weekday())).isoformat()
        for f, v in c.items():
            wkagg[wk][f] += v
    print("| Week (Mon) | Requests | cache_read | cache_write | input | output |")
    print("|------------|---------:|-----------:|------------:|------:|-------:|")
    for wk in sorted(wkagg):
        c = wkagg[wk]
        print(f"| {wk} | {c['n']:,} | {fmt(c['cR'])} | {fmt(c['cW'])} | {fmt(c['in'])} | {fmt(c['out'])} |")

    # daily
    print("\n| Date | Requests | cache_read | cache_write | input | output | Opus % | Sonnet % | Fable % |")
    print("|------|---------:|-----------:|------------:|------:|-------:|-------:|---------:|--------:|")
    cur = None
    for k in sorted(day):
        d = dt.date.fromisoformat(k)
        wk = (d - dt.timedelta(days=d.weekday())).isoformat()
        if wk != cur:
            cur = wk
            print(f"| **Week of {d.strftime('%b %-d')}** | | | | | | | | |")
        c = day[k]
        t = sum(fam_tok[k].values()) or 1
        ft = fam_tok[k]
        print(f"| {d.strftime('%b %-d %a')} | {c['n']:,} | {fmt(c['cR'])} | {fmt(c['cW'])} | {fmt(c['in'])} | {fmt(c['out'])} "
              f"| {100 * ft['opus'] / t:.0f} | {100 * ft['sonnet'] / t:.0f} | {100 * ft['fable'] / t:.0f} |")
    return 0


if __name__ == "__main__":
    sys.exit(main())

</details>

View original on GitHub ↗