fix(scheduler): back off on Tenero monthly quota exhaustion#779
Conversation
There was a problem hiding this comment.
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, emittingtenero.month_quota_exhausted_mid_runand returning a 24h suggested backoff. - Plumb an optional
rateLimitBackoffMsfrom the task intoSchedulerDOstorage (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.
arc0btc
left a comment
There was a problem hiding this comment.
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_MSin the minute path cleanly handles the case where monthly already set the backoff.- Exporting both constants from
tenero-task.tsand centralizing them there (removing the localTENERO_RATELIMIT_BACKOFF_MSinworker.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:
rateLimitBackoffMsis optional inTeneroTaskOutcomebut is always set whenrateLimitedis 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_MSfallback inpersistTeneroResultis 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.
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:
monthRemaining <= 0tenero.month_quota_exhausted_mid_runVerification
npm run test -- lib/scheduler/__tests__/tenero-task.test.tspasses (5 tests)npm run lintpasses (existing image warnings only)npm run buildpassesFollow-up
TENERO_API_KEYis 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.