Repository navigation
ccusage: push each cycle's readings to an external dashboard - #587
Merged
Merged
Conversation
An optional pusher for the Claude account usage poller: with
MUSTER_CC_USAGE_PUSH_URL (https; http only to loopback) and
MUSTER_CC_USAGE_PUSH_TOKEN (Bearer), after each poll cycle muster PUTs
{source_instance, read_at, accounts} once, 10s timeout, expecting 204.
accounts is built by the same ccUsageAccounts the GET
/api/cc-accounts/usage handler uses, so the two are byte-for-byte equal.
A failure is logged by class only (never the token, body or answer),
counted in muster_cc_usage_push_failures_total{reason}, and not retried
until the next cycle; redirects are not followed.
Boot is refused when the URL is set with the poller off, without the
token, or not https, and when the token or instance is set without the
URL. MUSTER_CC_USAGE_PUSH_INSTANCE (<=64 chars) defaults to
MUSTER_PUBLIC_URL's host, else "muster".
Tests: golden (pushed accounts == API accounts, used_by included),
refusal matrix, 204 vs 4xx/5xx/200/redirect/timeout/transport/build,
no token or body in logs, one AfterPass per pass. 24 mutants, all killed
by the specific assertion.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The push body gains the API's own top-level poll_interval_seconds (the poller's configured interval in whole seconds), so the dashboard can mark a source stale at a multiple of it. accounts stays byte-for-byte the API's. The golden pins it equal to the API's and to 420 / 0 for a 7m / off interval (three mutants: hardcoded, omitted, minutes; all killed). Fixes CI's two ledger failures from the first push: the three MUSTER_CC_USAGE_PUSH_* variables join the Claude Code agents feature's settings and the banner exemptions (each reason read off the code). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Round 0 (requirements and deletion): proceed. D1 is kept: the instance label is cheap, and it names the source on the dashboard. Round 1 (nine-axis checklist) at
|
From the #587 audit: a dashboard refusing the push (a cap exceeded) said only "HTTP 400" every cycle. On a 4xx the first 512 bytes of its answer are now logged as one line: control characters made spaces, cut at 200 runes, and the token redacted should the dashboard echo it. The request body is still never logged. A push cut short by muster shutting down (the parent context ended) is now counted as reason "canceled", not "timeout" or "transport". Tests: the 400 answer appears as one sanitized line (with the token redacted, cut on a rune boundary), and a shutdown counts as canceled. Mutants: no redaction, no control-character strip, no cut, a cut by bytes, the answer not logged, the token logged, canceled reported as timeout, and no canceled branch. Every one was killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #577: the per-account Claude usage poller can now push its readings to an external dashboard after each cycle, instead of only being pulled.
Env contract
MUSTER_CC_USAGE_PUSH_URLhttpsURL (plainhttponly to loopback); no userinfo. Refused withoutMUSTER_CC_USAGE_POLL.MUSTER_CC_USAGE_PUSH_TOKENAuthorization: Bearer. Never logged.MUSTER_CC_USAGE_PUSH_INSTANCEMUSTER_PUBLIC_URLhost, elsemusterThe token or instance set without the URL is refused too (a knob that does nothing).
Behaviour
Poller.AfterPass, once per pass, inline in the loop):PUT{"source_instance", "read_at", "poll_interval_seconds", "accounts": [...]}(poll_interval_secondsis the API's own field: the configured interval in whole seconds, 0 with the poller off); 10s timeout; expects204.accountscomes from the sameccUsageAccountstheGET /api/cc-accounts/usagehandler uses, for the admin owner, so the entries are byte-for-byte the API's,used_byincluded.muster_cc_usage_push_failures_total{reason}(build|timeout|transport|http_4xx|http_5xx|http_other|canceled), and not retried until the next cycle. Redirects are not followed. On a 4xx the dashboard's answer (first 512 bytes) is logged as one line: control characters stripped, cut at 200 runes, the token redacted. A push cut short by shutdown counts ascanceled.NOT_FORWARDEDledger,configuration.md, thejobs.md"Pushing the readings" section and the metric ledger are all updated.Tests
poll_interval_secondsequals the API's value, and reads 420 / 0 for a 7m / off interval. The pushedaccountsequals the API'saccountsbyte-for-byte, with a positive control showing the fixture has attribution and extra windows.http_4xx, 500/503 →http_5xx, 200 and redirect →http_other, slow →timeout, closed port →transport, store error →buildwith no request. Each makes exactly one request and counts under exactly its own reason.RuncallsAfterPassexactly once per pass, after the pass.🤖 Generated with Claude Code