Skip to content

docs: add nvm compatible wrapper - #15

Merged
cnighswonger merged 1 commit into
cnighswonger:mainfrom
arjansingh:patch-1
Apr 13, 2026
Merged

docs: add nvm compatible wrapper#15
cnighswonger merged 1 commit into
cnighswonger:mainfrom
arjansingh:patch-1

Conversation

@arjansingh

Copy link
Copy Markdown
Contributor

The suggested wrapper script wasn't playing nice with nvm because of all the relative paths.

Use npm root to make sure we're always executing from where the global version is installed.

The suggested wrapper script wasn't playing nice with nvm because of all the relative paths.

Use `npm root` to make sure we're always executing from where the global version is installed.
@cnighswonger cnighswonger added bug Something isn't working P1 High — near-term target labels Apr 13, 2026
@cnighswonger
cnighswonger merged commit 1052258 into cnighswonger:main Apr 13, 2026
5 checks passed
cnighswonger added a commit that referenced this pull request Apr 13, 2026
nvm-compatible wrapper with dynamic npm root -g path resolution (PR #15).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cnighswonger added a commit that referenced this pull request Apr 25, 2026
Same correction Codex caught on PR #80: PIPE_BUF atomicity applies to
pipes/FIFOs only, not regular files. Previous draft overclaimed.

Honest contract: POSIX guarantees O_APPEND offset+write atomicity (no
torn offsets), NOT non-interleaved writes on regular files. Linux
ext4/xfs/btrfs empirically don't interleave single write(2) of <4KB.
Test #15 validates empirically. Escalation paths documented.

Pushing the same correction across all three v3.2.0 directives so
implementation doesn't inherit the wrong technical claim.
cnighswonger added a commit that referenced this pull request Apr 25, 2026
Same correction Codex caught on PR #80: PIPE_BUF atomicity applies to
pipes/FIFOs only, not regular files. Previous draft overclaimed.

Honest contract: POSIX guarantees O_APPEND offset+write atomicity (no
torn offsets), NOT non-interleaved writes on regular files. Linux
ext4/xfs/btrfs empirically don't interleave single write(2) of <4KB.
Test #15 validates empirically. Escalation paths documented.

Pushing the same correction across all three v3.2.0 directives so
implementation doesn't inherit the wrong technical claim.
cnighswonger added a commit that referenced this pull request Apr 25, 2026
Rewrite proxy/extensions/usage-log.mjs to emit the exact MeterRowSchema
v:1 shape that claude-code-meter validates today. The proxy's
~/.claude/usage.jsonl becomes the canonical wire format both repos
agree on. claude-meter's strict z.strictObject validator stays
unchanged.

Field shape (29 fields, exact MeterRowSchema v:1):
- v: literal 1
- ts, sid (8-char hex, sticky for proxy lifetime)
- model, requested_model?, model_mismatch?
- speed (enum), service_tier
- input/output/cache_creation/cache_read/ephemeral_1h/ephemeral_5m/
  web_search_requests
- q5h, q7d, q5h_reset, q7d_reset
- qstatus, qoverage, qclaim, qfallback_pct
- qoverage_util?, qrepresentative_claim?, org_id?, overage_disabled_reason?
- cache_hit_rate, q5h_delta, q7d_delta

Lifecycle:
- message_start: extract per-response state into ctx.meta._usageLog.start
  (model, speed, service_tier, input/cache split, ephemeral split,
  web_search_requests).
- message_delta: read from ctx.meta._usageLog, add output_tokens, parse
  quota headers, assemble final record with sid + delta tracking,
  append to JSONL.

Privacy:
- org_id is hashed with sha256(raw).digest("hex").slice(0, 16) — exact
  same algorithm as claude-meter, never raw. Test 11/11b enforce this:
  test 11 asserts bit-exact match; test 11b plants a secret raw org_id
  and asserts it doesn't appear anywhere in the assembled record.

Activation: enabled:false default in extension export (existing
usage-log pattern). Users opt in by adding entry to proxy/extensions.json:
  "usage-log": { "enabled": true, "order": 650 }
CACHE_FIX_USAGE_LOG=<path> overrides destination only — NOT an enable
flag.

Old 9-field shape (with peak_hour) is replaced wholesale. peak_hour is
NOT in MeterRowSchema and is dropped from the wire format
(recomputable from ts if needed). Old rows in users' existing
~/.claude/usage.jsonl will fail claude-meter's strict validator and
be skipped on the reader side — documented in CHANGELOG (separate
PR will add the CHANGELOG entry).

Atomicity: single-syscall fs.promises.appendFile of record + "\n".
POSIX O_APPEND offset+write atomicity (no torn offsets); non-
interleaved regular-file writes are empirical Linux behavior, not
POSIX. Test #15 (50 parallel writes) validates.

Cross-repo release ordering (per directive): cache-fix v3.2.0 ships
first; claude-meter v0.4.0 follows declaring >= cache-fix 3.2.0 as
required producer. Two PRs are NOT independently shippable.

21 tests, all green. Full suite 500/500.
cnighswonger added a commit that referenced this pull request Apr 25, 2026
* directive: claude-meter compat with proxy mode (#70)

Cross-repo refactor: expand proxy/extensions/usage-log.mjs to a v:2
superset schema matching claude-meter's MeterRowSchema, then refactor
claude-meter to consume from ~/.claude/usage.jsonl instead of via the
NODE_OPTIONS preload that CC v2.1.113+ Bun binary ignores.

Cache-fix side: schema expansion, sessionId, q5h/q7d delta tracking,
documented schema-version 2. Default enabled:false for v3.2.0;
default-on deferred to v3.3.0.

Claude-meter side (separate PR in that repo): new jsonl-tailer ingest
mode, deprecate preload export, update README.

* review: add PR 81 claude-meter compat directive review

* directive: address Codex review on claude-meter compat (#81)

Substantial rewrite. Anchored the directive on a single coherent wire
contract: proxy emits EXACTLY MeterRowSchema v:1 (the strict validator
already in claude-meter). No new schema version, no proxy-only extras.

Fixes for the six blockers:

1. Schema strictObject contradiction — abandoned the v:2 superset.
   Proxy now emits exact v:1 records. peak_hour dropped from output
   (recomputable from ts). claude-meter validator stays strict and
   unchanged. Single source of truth: MeterRowSchema in claude-meter.

2. Activation/config — corrected to match the actual usage-log pattern:
   enabled:false is the extension's own export default, opt-in is a
   JSON edit to extensions.json adding {"enabled": true, "order": 650}.
   CACHE_FIX_USAGE_LOG is path override only, never an enable flag.
   No extensions.json comments (it's JSON.parse'd).

3. org_id privacy — pinned to claude-meter's exact algorithm:
   sha256(raw).digest("hex").slice(0, 16). Specified in field-source
   table, in implementation choice, in tests, and in reviewer checklist.

4. State capture — explicit per-event responsibility documented.
   message_start populates ctx.meta._usageLog; message_delta reads from
   it and adds output_tokens. Test plan #4 and #5 cover this directly.

5. Migration/versioning — explicit cross-repo release ordering: proxy
   ships first, claude-meter v0.4.0 follows declaring >= cache-fix
   3.2.0. Old 9-field rows fail strict validation and are skipped
   with a debug log on the reader side; documented in CHANGELOG.

6. Test path — corrected to test/proxy-usage-log.test.mjs (existing
   file). All file references updated.

Plus: full per-field source table (29 fields, every MeterRowSchema
field with its exact source), expanded reviewer checklist (16 items),
expanded out-of-scope.

* directive: correct JSONL atomicity claim on claude-meter compat (#81)

Same correction Codex caught on PR #80: PIPE_BUF atomicity applies to
pipes/FIFOs only, not regular files. Previous draft overclaimed.

Honest contract: POSIX guarantees O_APPEND offset+write atomicity (no
torn offsets), NOT non-interleaved writes on regular files. Linux
ext4/xfs/btrfs empirically don't interleave single write(2) of <4KB.
Test #15 validates empirically. Escalation paths documented.

Pushing the same correction across all three v3.2.0 directives so
implementation doesn't inherit the wrong technical claim.

* feat: usage-log emits MeterRowSchema v:1 for claude-meter compat (#70)

Rewrite proxy/extensions/usage-log.mjs to emit the exact MeterRowSchema
v:1 shape that claude-code-meter validates today. The proxy's
~/.claude/usage.jsonl becomes the canonical wire format both repos
agree on. claude-meter's strict z.strictObject validator stays
unchanged.

Field shape (29 fields, exact MeterRowSchema v:1):
- v: literal 1
- ts, sid (8-char hex, sticky for proxy lifetime)
- model, requested_model?, model_mismatch?
- speed (enum), service_tier
- input/output/cache_creation/cache_read/ephemeral_1h/ephemeral_5m/
  web_search_requests
- q5h, q7d, q5h_reset, q7d_reset
- qstatus, qoverage, qclaim, qfallback_pct
- qoverage_util?, qrepresentative_claim?, org_id?, overage_disabled_reason?
- cache_hit_rate, q5h_delta, q7d_delta

Lifecycle:
- message_start: extract per-response state into ctx.meta._usageLog.start
  (model, speed, service_tier, input/cache split, ephemeral split,
  web_search_requests).
- message_delta: read from ctx.meta._usageLog, add output_tokens, parse
  quota headers, assemble final record with sid + delta tracking,
  append to JSONL.

Privacy:
- org_id is hashed with sha256(raw).digest("hex").slice(0, 16) — exact
  same algorithm as claude-meter, never raw. Test 11/11b enforce this:
  test 11 asserts bit-exact match; test 11b plants a secret raw org_id
  and asserts it doesn't appear anywhere in the assembled record.

Activation: enabled:false default in extension export (existing
usage-log pattern). Users opt in by adding entry to proxy/extensions.json:
  "usage-log": { "enabled": true, "order": 650 }
CACHE_FIX_USAGE_LOG=<path> overrides destination only — NOT an enable
flag.

Old 9-field shape (with peak_hour) is replaced wholesale. peak_hour is
NOT in MeterRowSchema and is dropped from the wire format
(recomputable from ts if needed). Old rows in users' existing
~/.claude/usage.jsonl will fail claude-meter's strict validator and
be skipped on the reader side — documented in CHANGELOG (separate
PR will add the CHANGELOG entry).

Atomicity: single-syscall fs.promises.appendFile of record + "\n".
POSIX O_APPEND offset+write atomicity (no torn offsets); non-
interleaved regular-file writes are empirical Linux behavior, not
POSIX. Test #15 (50 parallel writes) validates.

Cross-repo release ordering (per directive): cache-fix v3.2.0 ships
first; claude-meter v0.4.0 follows declaring >= cache-fix 3.2.0 as
required producer. Two PRs are NOT independently shippable.

21 tests, all green. Full suite 500/500.

* review: PR 81 claude-meter compat implementation review
vsits-proxy-builder Bot pushed a commit that referenced this pull request May 3, 2026
…ead review)

The directive's auto-upgrade formula
  ttlValue === "5m" || detectedTier === "5m" ? "5m" : "1h"
was tested for env=5m-wins (#14) and env=none-suppresses (#15) but not
for explicit env=1h with detected=5m — the case the bug actually
breaks. Lead flagged this in PR #100 review as the most important
contract to lock in. Without it, a regression that silently fell back
to ttlValue (ignoring detectedTier) would pass the rest of the suite.

Issue-number transposition in lead's review was clarified separately
(PR #100 directive ref #97 is correct; #98 was the image-strip issue
already closed by PR #99).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cnighswonger pushed a commit that referenced this pull request May 3, 2026
* directive: TTL tier detection for proxy ttl-management (#97)

Port preload.mjs's in-payload 5m-tier detection (lines 1815-1828) to the
proxy ttl-management extension so users on CC v2.1.113+ stop losing this
fix. Pure in-payload approach matches preload's proven mechanism; quota-
header subscription is deferred to v2 if real-world telemetry shows v1
leaves a gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: codex review of PR #100 directive (TTL tier detection)

* directive: revise TTL tier detection — split detection into pre-normalize extension

Codex review of PR #100 flagged a blocking pipeline-order issue:
cache-control-normalize (order 400) strips user-message cache_control
markers before ttl-management (order 500) can read them, so the
proposed in-payload detection would only see body.system markers.

Revised approach: split detection into a new ttl-tier-detect extension
at order 350 (before normalize), which sets ctx.meta._ttlTier. ttl-
management consumes the flag at order 500 instead of scanning the body
itself. Adds a pipeline-level integration test that exercises the real
extension order to lock the rewire in.

Refs Codex review at docs/code-reviews/pr-100-ttl-tier-detection-directive-review-2026-05-03.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: codex re-review of PR #100 directive (TTL tier detection)

* directive: revise TTL tier detection — move detection to order 75 with full audit

Codex re-review of PR #100 flagged a second pipeline-order miss: fresh-
session-sort (order 250) destructures and discards cache_control on
relocatable user blocks (lines 140, 164), which would lose a ttl=5m
signal on, e.g., a relocated skills block before order 350 detection
could see it.

Revised: move ttl-tier-detect to order 75 (between read-only
upstream-change-detection at 50 and output-efficiency-rewrite at 90),
which is provably pre-mutation by every existing extension. Includes a
full audit table of all 20 pipeline extensions classified by whether
they touch cache_control. Pipeline tests reworded to assert observable
output facts rather than 'ran first', and a fresh-session-sort
relocatable-block regression test added (test #19).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: codex second re-review of PR #100 directive (TTL tier detection)

* directive: third revision — fix audit orders and stale Acceptance reference

Codex's second re-review of PR #100 flagged:
- B: Acceptance section still referenced order 350 (stale from prior draft).
- B: Audit table had wrong orders for content-strip and tool-input-normalize
  (had 350 and 280; actual extensions.json values are 330 and 340).
- N: Test #19 wording overclaimed it could prove fresh-session-sort
  specifically did the strip; rephrased to assert observable end-state
  with the causal attribution moved to a static-reading citation.

Audit table now reflects the live pipeline.mjs:23-25 logic
(extensions.json overrides; .mjs defaults as fallback) and includes a
Source/Default-enabled column for transparency on which extensions
actually load by default.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: codex third re-review of PR #100 directive (TTL tier detection)

* directive: add test #22 covering env=1h + detected=5m auto-upgrade (lead review)

The directive's auto-upgrade formula
  ttlValue === "5m" || detectedTier === "5m" ? "5m" : "1h"
was tested for env=5m-wins (#14) and env=none-suppresses (#15) but not
for explicit env=1h with detected=5m — the case the bug actually
breaks. Lead flagged this in PR #100 review as the most important
contract to lock in. Without it, a regression that silently fell back
to ttlValue (ignoring detectedTier) would pass the rest of the suite.

Issue-number transposition in lead's review was clarified separately
(PR #100 directive ref #97 is correct; #98 was the image-strip issue
already closed by PR #99).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(proxy): ttl-tier-detect extension + ttl-management auto-upgrade (#97)

Port preload.mjs:1815-1828 in-payload TTL tier detection to the proxy
pipeline. Adds a new ttl-tier-detect extension at order 75 that scans
body.system and body.messages[*].content[*] for any cache_control.ttl
="5m" and stashes ctx.meta._ttlTier. Order 75 sits before every
extension that mutates cache_control (fresh-session-sort at 250,
cache-control-normalize at 400, messages-cache-breakpoint at 410), so
the signal survives downstream strips.

ttl-management at order 500 now reads ctx.meta._ttlTier and applies
preload's formula: ttlValue === "5m" || detectedTier === "5m" ? "5m"
: "1h". Auto-detection upgrades effective 1h to 5m, never the reverse,
never overrides explicit env "none".

Closes the regression where users on CC v2.1.113+ silently injected
mismatched TTL markers after Q5h saturation, which caused 1h-after-5m
processing-order rejection and cache misses.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(proxy): TTL tier detection — unit + pipeline-level coverage (#97)

22 new tests across three files mapping to directive tests #1-#22:

- test/proxy-ttl-tier-detect.test.mjs (#1-#11): unit tests on
  detectExistingTier and the extension's onRequest hook (purity,
  idempotency, no body mutation).
- test/proxy-ttl-management.test.mjs (#12-#17): consumer behavior —
  env=5m wins, env=none suppresses, env=1h upgrades to 5m on detected,
  subagent path, no overwrite of existing ttl.
- test/proxy-ttl-tier-pipeline.test.mjs (#18-#22): pipeline-level
  integration via loadExtensions + runOnRequest, asserting end-state
  observables across the real extension order. Includes:
    #18 cache-control-normalize regression (user-message marker stripped)
    #19 fresh-session-sort regression (relocatable <skills> block stripped)
    #20 negative case (pure-1h payload)
    #21 env=none precedence
    #22 lead-flagged auto-upgrade (env=1h + detected=5m)

Suite: 676 → 698 passing, +22 net.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: codex implementation review of PR #100 (TTL tier detection)

* test: tighten pipeline #18 geometry + #19 assertion (Codex impl review)

#18 was misconstructed: the only ttl=5m marker sat on the last block,
which cache-control-normalize then re-applied to the same block — so a
naive implementation that scanned post-normalize would also pass. Move
the marker to the FIRST block of a user message whose LAST block has
no cache_control on input. Now normalize strips at one position and
re-applies the canonical at a different one, and the test only passes
if detection ran pre-strip (i.e., the order-75 rewire works).

#19 now asserts the relocated <skills> block has no cache_control on
output, providing observable evidence that fresh-session-sort
destructured the field per fresh-session-sort.mjs:140/:164 and the
detection at order 75 is what saved the signal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: codex impl re-review of PR #100 (TTL tier detection)

---------

Co-authored-by: vsits-codex-review-agent[bot] <279859562+vsits-codex-review-agent[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
vsits-proxy-builder Bot added a commit that referenced this pull request May 5, 2026
)

cache-telemetry rewrite:
- Resolve session id from x-claude-code-session-id (canonical), with
  fallbacks to x-session-id and x-anthropic-session-id.
- Apply canonical sessionFilename(rawId) rule: allowlist
  [A-Za-z0-9_-]{1,128} for raw passthrough, else inv-<sha256(raw)[:16]>;
  null/empty → "unknown". Path-traversal-safe by construction.
- Write split layout:
  - ~/.claude/quota-status/account.json (account-global facts; fresh on
    every response)
  - ~/.claude/quota-status/sessions/<filename>.json (per-session cache
    facts; only updated when that session requests)
- Atomic writes: writeFileSync to .tmp.<pid>.<crypto.randomBytes(4)> then
  renameSync. No partial reads possible.
- Legacy ~/.claude/quota-status.json deleted on first invocation per
  process (one-shot via module-state flag) — removes stale-artifact
  footgun for post-upgrade consumers.
- TTL sweep on sessions/ only, throttled to 60s, configurable via
  CACHE_FIX_QUOTA_STATUS_TTL_DAYS (default 7).

microcompact-stability:
- Session-id fallback chain now includes x-claude-code-session-id
  ahead of legacy x-session-id and x-anthropic-session-id. Was returning
  null for most CC requests because the canonical header wasn't in the
  chain.

Tests:
- 22 new cache-telemetry tests covering filename rule (#11a-#11i),
  file-write happy path + fallbacks (#1-#4), two-session interleaving
  (#5), quota-only skip (#6), atomic write contract (#7), legacy cleanup
  (#7a-#7b), TTL sweep behavior (#8-#11).
- 4 new microcompact tests covering the new fallback chain (#12-#15).

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cnighswonger pushed a commit that referenced this pull request May 5, 2026
* directive: per-session quota-status files (#104)

Replace proxy-global ~/.claude/quota-status.json with split account.json +
per-session <session-id>.json, eliminating cross-session contamination on
multi-agent hosts. Folds in microcompact-stability fallback-chain fix to
recognise the canonical x-claude-code-session-id header.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* directive: address Lead's review (#105)

- Add legacy ~/.claude/quota-status.json cleanup on first invocation per
  process (Lead's should-fix #1) — removes the stale-artifact footgun for
  post-upgrade consumers that haven't migrated.
- Switch atomic-write tmp suffix from Math.random() to
  crypto.randomBytes(4).toString('hex') (Lead's should-tighten #2) —
  collision-resistant if a future change introduces multiple writers.
- Drop "or concurrent" framing in test #17; specify back-to-back with
  rationale (Lead's nit #3) — JS is single-threaded; back-to-back is the
  reliably-testable shape that exercises the same invariant.
- Add tests #7a/#7b for legacy cleanup behaviour (one-shot per process,
  silent on absence).

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review: capture Codex directive findings for PR 105

* directive: address Codex's blocker — define filename rule (#105)

- Define canonical session-id-to-filename derivation rule:
  - Allowlist [A-Za-z0-9_-]{1,128} for raw passthrough; otherwise
    inv-<sha256(rawId)[:16]> for malformed/oversized inputs.
  - Empty/null → "unknown".
  - Both writer (cache-telemetry) and readers (tools/) must apply the
    same rule.
- Restructure layout: per-session files live under
  ~/.claude/quota-status/sessions/, account stays at top level. The
  subdirectory split makes reserved-name collisions structurally
  impossible.
- Add tests #11a–#11j for filename rule (UUID, alphanumeric, empty,
  path-traversal, length cap, hash determinism, reserved names, pipeline
  integration with malformed input).
- Add T1–T5 smoke tests for tools/quota-statusline.sh (Codex's
  needs-attention #3).
- Expand migration table to include README sweep + tracked-issues docs
  (Codex's needs-attention #2).
- Soften llm-relay external citation, add locally-verifiable corroboration
  (Codex's needs-attention #1).
- Update acceptance criteria to cover the filename rule, doc sweep, and
  smoke test pass.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* directive: normalize all paths to the sessions/ subdirectory layout (#105)

Codex re-review caught that the design section adopted
~/.claude/quota-status/sessions/<filename>.json but stale references
to the old root-level layout still appeared in:

- CHANGELOG snippet (intro paragraph + ### Changed entry)
- Tests #1-#5 (per-session file paths)
- Sweep test #8 (stub directory + sweep boundary)
- Sweep tests #9-#11 (per-session file location + account.json carve-out)
- Test #11j (self-contradiction about whether account.json is written)
- Acceptance bullet #1

All references now consistently describe the sessions/ subdirectory
layout. Tests #8-#11 explicitly call out account.json living at the
parent directory and being untouched by the sweep. Test #11j clarified
that account.json IS expected to be written; the path-traversal-safety
assertion is about no files appearing OUTSIDE
~/.claude/quota-status/sessions/.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review: capture Codex PR 105 directive re-review

* directive: address Codex re-review findings (#105)

- Replace literal NUL byte in test #11d with the textual escape \0,
  restoring the directive file to plain UTF-8 text. Standard tooling
  (rg, grep, file) was treating it as binary because of the embedded
  NUL — defeating the purpose of including the test sample at all.
- Update llm-relay citation path: src/llm_relay/proxy/proxy.py is the
  canonical location on cnighswonger/llm-relay (Codex now has read
  access to that repo and verified). Also strengthens the framing —
  citation is now mechanically verified rather than just "Lead said
  so."

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review: approve quota-status directive rereview2

* feat: per-session quota-status files + microcompact session-id fix (#104)

cache-telemetry rewrite:
- Resolve session id from x-claude-code-session-id (canonical), with
  fallbacks to x-session-id and x-anthropic-session-id.
- Apply canonical sessionFilename(rawId) rule: allowlist
  [A-Za-z0-9_-]{1,128} for raw passthrough, else inv-<sha256(raw)[:16]>;
  null/empty → "unknown". Path-traversal-safe by construction.
- Write split layout:
  - ~/.claude/quota-status/account.json (account-global facts; fresh on
    every response)
  - ~/.claude/quota-status/sessions/<filename>.json (per-session cache
    facts; only updated when that session requests)
- Atomic writes: writeFileSync to .tmp.<pid>.<crypto.randomBytes(4)> then
  renameSync. No partial reads possible.
- Legacy ~/.claude/quota-status.json deleted on first invocation per
  process (one-shot via module-state flag) — removes stale-artifact
  footgun for post-upgrade consumers.
- TTL sweep on sessions/ only, throttled to 60s, configurable via
  CACHE_FIX_QUOTA_STATUS_TTL_DAYS (default 7).

microcompact-stability:
- Session-id fallback chain now includes x-claude-code-session-id
  ahead of legacy x-session-id and x-anthropic-session-id. Was returning
  null for most CC requests because the canonical header wasn't in the
  chain.

Tests:
- 22 new cache-telemetry tests covering filename rule (#11a-#11i),
  file-write happy path + fallbacks (#1-#4), two-session interleaving
  (#5), quota-only skip (#6), atomic write contract (#7), legacy cleanup
  (#7a-#7b), TTL sweep behavior (#8-#11).
- 4 new microcompact tests covering the new fallback chain (#12-#15).

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: migrate shipped tools + pipeline integration tests (#104)

Tools migration to per-session quota-status layout:

- tools/quota-statusline.sh: now reads CC's hook-stdin session_id, derives
  the per-session filename via the canonical rule (inline Python
  reimplementation of sessionFilename()), and reads
  ~/.claude/quota-status/account.json + sessions/<filename>.json. Falls
  back gracefully on missing session_id (quota only, no TTL block) and
  missing per-session file (warming-state aware).
- tools/cache-test.sh: snapshot_cache helper now reads the most-recent
  per-session file in ~/.claude/quota-status/sessions/ (each one-shot CC
  invocation generates its own session_id, so latest mtime = the call we
  just made). The directive's "swap QUOTA_FILE → account.json"
  prescription would have lost the cache.* fields the script needs;
  reading the latest session file preserves them correctly.
- tools/cross-version-cache-test.sh: account.json for the global Q5h
  read (the four spots in the script that read five_hour.pct).

Tests:
- New test/quota-statusline-smoke.test.mjs covers T1-T5 (UUID happy path,
  missing session_id fallback, warming-state, all-files-missing clean
  exit, malformed session_id reading the hashed filename).
- New test/proxy-quota-status-pipeline.test.mjs covers the integration
  tests #16 (end-to-end happy path), #17 (two-session interleaving), and
  #11j (path-traversal safety) by driving the full extension pipeline
  from loadExtensions() through onResponseStart + onStreamEvent.

Full suite: 698 → 732 tests, all passing.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: update README/translations + CHANGELOG for per-session quota-status (#104)

- README.md, README.zh.md, README.ko.md: update three references each.
  (1) extension table description for `cache-telemetry` reflects the
  split account+sessions layout. (2) status-line section explains the
  per-session split for proxy mode and notes preload mode keeps the
  legacy single-file path. (3) monitoring section disambiguates between
  the two paths.
- CHANGELOG.md: new ### Changed entry under [Unreleased] flagging the
  breaking path change with full migration context, the filename-rule
  shape, and the TTL/sweep env knob. ### Fixed entry for the
  microcompact session-id fallback chain. ### Tests entry for the
  698 → 732 count.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review: capture PR 105 implementation findings

* fix: address Codex implementation-review findings (#105)

Blocking finding — quota-statusline.sh filename-rule consistency:

The writer (cache-telemetry.mjs:sessionFilename) maps null/undefined/""/
whitespace-only ids to 'unknown' via the canonical rule. The shell
reader was using `sess_id = stdin_data.get('session_id') or ''` which
short-circuits for null and "" (skipping the per-session read entirely)
but works for whitespace-only (which becomes a non-empty string and
flows through session_filename). That asymmetry meant the writer/reader
contract wasn't identical for null/empty inputs.

Fix: pass the raw stdin value (including null/empty) directly into
session_filename and always attempt to read sessions/<filename>.json.
The "file doesn't exist" fallback already covers the "no
sessions/unknown.json on disk" case gracefully — statusline shows
account-only just like before, but for the right reason (file
missing) instead of the wrong one (rule short-circuit).

Test additions:
- T2a covers null, "", "   ", and "\t\n" inputs all reading
  sessions/unknown.json when it exists. Locks in the contract symmetry.

Doc-sweep gaps (Codex's needs-attention):
- docs/monitoring.md: quota-tracking and peak-hour-detection sections
  now describe the proxy/preload split and the per-session layout.
- docs/guia-pt-br.md: debug snippet shows account.json as the proxy
  path and notes preload.json as legacy.

Test count: 732 → 733.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: extension-impact-guide cache-telemetry path (#105)

Codex's implementation re-review flagged this as a non-blocking new doc
finding at docs/extension-impact-guide.md:84 — stale single-file path
remained after the per-session sweep. Updated to describe the new split
layout.

Ref #104

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review: approve PR 105 implementation rereview

---------

Co-authored-by: vsits-proxy-builder[bot] <279815601+vsits-proxy-builder[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: vsits-codex-review-agent[bot] <279859562+vsits-codex-review-agent[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working P1 High — near-term target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants