diff --git a/headroom/cli/proxy.py b/headroom/cli/proxy.py index 8a222178f..738d7d8e4 100644 --- a/headroom/cli/proxy.py +++ b/headroom/cli/proxy.py @@ -57,6 +57,26 @@ from .main import main @click.option("--no-optimize", is_flag=True, help="Disable optimization (passthrough mode)") @click.option("--no-cache", is_flag=True, help="Disable semantic caching") @click.option("--no-rate-limit", is_flag=True, help="Disable rate limiting") +@click.option( + "--no-subscription-tracking", + is_flag=True, + envvar="HEADROOM_NO_SUBSCRIPTION_TRACKING", + help=( + "Disable the Anthropic Claude Code subscription usage poller " + "(GET /api/oauth/usage). Env: HEADROOM_NO_SUBSCRIPTION_TRACKING." + ), +) +@click.option( + "--subscription-poll-interval", + type=int, + default=None, + envvar="HEADROOM_SUBSCRIPTION_POLL_INTERVAL", + help=( + "Seconds between Anthropic subscription usage polls (1–3600, default 300). " + "Lower values give fresher /stats but risk 429s from Anthropic. " + "Env: HEADROOM_SUBSCRIPTION_POLL_INTERVAL." + ), +) @click.option( "--retry-max-attempts", type=int, @@ -254,6 +274,8 @@ def proxy( no_optimize: bool, no_cache: bool, no_rate_limit: bool, + no_subscription_tracking: bool, + subscription_poll_interval: int | None, retry_max_attempts: int | None, connect_timeout_seconds: int | None, anthropic_pre_upstream_concurrency: int | None, @@ -381,6 +403,10 @@ def proxy( optimize=not no_optimize, cache_enabled=not no_cache, rate_limit_enabled=not no_rate_limit, + subscription_tracking_enabled=not no_subscription_tracking, + subscription_poll_interval_s=( + subscription_poll_interval if subscription_poll_interval is not None else 300 + ), retry_max_attempts=retry_max_attempts if retry_max_attempts is not None else 3, connect_timeout_seconds=connect_timeout_seconds if connect_timeout_seconds is not None diff --git a/headroom/proxy/models.py b/headroom/proxy/models.py index 1838d015b..1bbf5f3fd 100644 --- a/headroom/proxy/models.py +++ b/headroom/proxy/models.py @@ -218,7 +218,7 @@ class ProxyConfig: # Subscription Window Tracking (Anthropic OAuth accounts) subscription_tracking_enabled: bool = True - subscription_poll_interval_s: int = 10 + subscription_poll_interval_s: int = 300 subscription_active_window_s: int = 60 # Stateless mode — disable all filesystem writes for read-only / container deployments diff --git a/headroom/subscription/tracker.py b/headroom/subscription/tracker.py index 602cee0dc..70e5e9467 100644 --- a/headroom/subscription/tracker.py +++ b/headroom/subscription/tracker.py @@ -43,7 +43,7 @@ from headroom.subscription.models import ( logger = logging.getLogger(__name__) -_DEFAULT_POLL_INTERVAL_S = 10 +_DEFAULT_POLL_INTERVAL_S = 300 _DEFAULT_ACTIVE_WINDOW_S = 60 _PERSIST_FILE_ENV = _paths.HEADROOM_SUBSCRIPTION_STATE_PATH_ENV _DEFAULT_PERSIST_DIR = ".headroom" @@ -70,7 +70,7 @@ class SubscriptionTracker(QuotaTracker): alongside the Codex and Copilot trackers. Args: - poll_interval_s: Seconds between polls while active (1–300). + poll_interval_s: Seconds between polls while active (1–3600, default 300). active_window_s: Seconds since last notify_active call that keeps polling alive (default 60 = 1 minute). enabled: Set to ``False`` to disable tracking (mirrors @@ -92,7 +92,7 @@ class SubscriptionTracker(QuotaTracker): client: SubscriptionClient | None = None, ) -> None: self._enabled = enabled - self._poll_interval_s = max(1, min(poll_interval_s, 300)) + self._poll_interval_s = max(1, min(poll_interval_s, 3600)) self._active_window_s = max(5.0, active_window_s) self._persist_path = persist_path or _get_persist_path() self._client = client or SubscriptionClient()