Skip to content

fix(scheduler): back off on Tenero monthly quota exhaustion#779

Merged
whoabuddy merged 1 commit into
mainfrom
hotfix/tenero-month-quota-backoff
May 12, 2026
Merged

fix(scheduler): back off on Tenero monthly quota exhaustion#779
whoabuddy merged 1 commit into
mainfrom
hotfix/tenero-month-quota-backoff

Conversation

@whoabuddy
Copy link
Copy Markdown
Contributor

Summary

Hotfix after #743 deploy verification: SchedulerDO is bound and running, but Tenero is returning 429 with x-ratelimit-month-remaining: 0. The current scheduler treats that like short-lived minute throttling, continues through the remaining token set, and only backs off for 5 minutes.

This change:

  • stops the Tenero task immediately when monthRemaining <= 0
  • logs tenero.month_quota_exhausted_mid_run
  • returns a 24h suggested backoff for monthly quota exhaustion
  • keeps minute-level throttling on the existing 5-minute backoff
  • adds regression coverage for the monthly-quota path

Verification

  • npm run test -- lib/scheduler/__tests__/tenero-task.test.ts passes (5 tests)
  • npm run lint passes (existing image warnings only)
  • npm run build passes

Follow-up

TENERO_API_KEY is not present in the Arc credential store. Prices will remain empty/null until a real Tenero key is provisioned as a Workers secret or the unauthenticated monthly quota resets.

Copilot AI review requested due to automatic review settings May 12, 2026 18:03
@whoabuddy whoabuddy merged commit 5ce2434 into main May 12, 2026
11 of 12 checks passed
@whoabuddy whoabuddy deleted the hotfix/tenero-month-quota-backoff branch May 12, 2026 18:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hotfix to the SchedulerDO’s Tenero price refresh task so it correctly detects monthly quota exhaustion (via x-ratelimit-month-remaining <= 0) and backs off for a much longer period instead of continuing to churn through tokens on repeated 429s.

Changes:

  • Add explicit monthly-quota exhaustion detection in runTeneroTask, emitting tenero.month_quota_exhausted_mid_run and returning a 24h suggested backoff.
  • Plumb an optional rateLimitBackoffMs from the task into SchedulerDO storage (nextRunAfter.tenero) while preserving the existing 5-minute minute-quota behavior.
  • Add a regression test covering the monthly-quota exhaustion path.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
worker.ts Uses task-provided rateLimitBackoffMs (or the minute default) when persisting Tenero adaptive backoff into DO storage.
lib/scheduler/tenero-task.ts Detects monthly quota exhaustion mid-run, stops early, logs a dedicated event, and returns a 24h backoff suggestion.
lib/scheduler/tests/tenero-task.test.ts Adds regression coverage asserting the new monthly-quota behavior and backoff propagation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/scheduler/__tests__/tenero-task.test.ts
Copy link
Copy Markdown
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stops the scheduler from burning through the remaining token set when monthly Tenero quota is exhausted — good call. The current behavior (treating a month-level 429 like a minute-level one and backing off for 5 minutes before continuing) could waste quota on re-entry. This makes the distinction explicit and gets out immediately.

What works well:

  • Month check placed before minute check — if both fire on the same token, the 24h backoff wins. Correct precedence.
  • rateLimitBackoffMs ??= TENERO_MINUTE_QUOTA_BACKOFF_MS in the minute path cleanly handles the case where monthly already set the backoff.
  • Exporting both constants from tenero-task.ts and centralizing them there (removing the local TENERO_RATELIMIT_BACKOFF_MS in worker.ts) is the right move — single source of truth.
  • Test coverage hits both paths and uses the exported constant directly (TENERO_MONTH_QUOTA_BACKOFF_MS) rather than hardcoding 86400000 — so if the value ever changes, the test stays consistent automatically.

[question] Monthly test expects 3 fetch calls for 2 token IDs (tenero-task.test.ts:229)

The minute-exhaustion test expects 1 fetch call when token 1 succeeds and token 2 returns 429. The monthly test also processes 2 token IDs but expects 3 fetch calls — even though the first 429 should cause an early break. Does fetchTokenPriceUsd retry on 429, or is this reflecting a batch-fetching implementation detail? Not blocking (tests pass), but worth a comment in the test explaining why.

Code quality notes:

  • rateLimitBackoffMs is optional in TeneroTaskOutcome but is always set when rateLimited is true. A discriminated union would encode this invariant in the type system ({ rateLimited: false } | { rateLimited: true; rateLimitBackoffMs: number }), but that's a refactor worth a separate pass — not a hotfix concern.
  • The ?? TENERO_MINUTE_QUOTA_BACKOFF_MS fallback in persistTeneroResult is defensive code for a state that can't actually occur (rateLimited true, backoffMs unset). Harmless, and arguably good belt-and-suspenders for a quota path you really don't want misbehaving.

Operational note: We run the Tenero price feed through our agent stack. Month-quota exhaustion mid-run is exactly the kind of silent failure that's hard to diagnose after the fact — the structured log event (tenero.month_quota_exhausted_mid_run) and the monthRemaining field in the outcome are good observability additions.

The TENERO_API_KEY note in the PR description is honest and appreciated — useful to know prices will be null until the secret is provisioned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants