Skip to content

Commit c5cf2ed

Browse files
committed
fix(tools): validate dict shape in read_quota_status() per Codex review
Codex's REQUEST_CHANGES review on PR #138 flagged that read_quota_status() returned json.load(f) directly without validating the payload is a dict. A candidate file containing valid JSON but a non-dict shape (e.g. [], null, "string", 123 — possible from a partial write or a misconfigured writer) would be returned as-is, breaking the documented "dict or None" contract. Downstream consumers calling status.get(...) on the result would then AttributeError instead of seeing the documented None-fallback behavior. Fix: after json.load, check isinstance(data, dict). If true, return it. If false, fall through to the next candidate path (or to None if no candidate yields a dict). Same defensive pattern that callers would otherwise have to apply at every call site. Docstring updated to describe the new behavior — a non-dict payload is skipped rather than returned, so callers can rely on the dict-or-None contract without an additional type check. Verified empirically with five malformed-payload cases: 1. Both files missing → None 2. New-path valid dict → returns it 3. New-path non-dict + legacy-path dict → returns legacy 4. Both non-dict → None 5. New-path malformed JSON + legacy-path dict → returns legacy All pass. — Proxy Builder
1 parent c3ef579 commit c5cf2ed

1 file changed

Lines changed: 13 additions & 3 deletions

File tree

tools/cache_analysis.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,15 @@ def read_quota_status():
150150
- v3.5.0+ (proxy mode, per-session split): ~/.claude/quota-status/account.json
151151
- v3.4.x and earlier (or preload mode): ~/.claude/quota-status.json (flat)
152152
153-
Tries the v3.5.0+ path first, falls back to the legacy flat path.
154-
Returns dict with five_hour/seven_day pct, or None if unavailable.
153+
Tries the v3.5.0+ path first, falls back to the legacy flat path. A
154+
candidate file whose JSON parses but isn't a dict (e.g. a partial write
155+
that lands as ``[]`` or ``null``) is skipped so the next candidate gets
156+
a chance — and so callers never receive a non-dict and break on
157+
``status.get(...)`` accessors downstream.
158+
159+
Returns dict with five_hour/seven_day pct (and other fields written by
160+
cache-fix's response-header capture), or None if no candidate yields a
161+
dict-shaped payload.
155162
"""
156163
import os
157164
for quota_file in (
@@ -160,9 +167,12 @@ def read_quota_status():
160167
):
161168
try:
162169
with open(quota_file) as f:
163-
return json.load(f)
170+
data = json.load(f)
164171
except (OSError, json.JSONDecodeError):
165172
continue
173+
if isinstance(data, dict):
174+
return data
175+
# Valid JSON but wrong shape — try the next candidate.
166176
return None
167177

168178

0 commit comments

Comments
 (0)