Repository navigation
Description
Description
Summary
OpenCode’s free-tier quota is enforced per egress IP address, not per user account (confirmed by maintainers in #9611 and visible in the codebase via the IpRateLimitTable). This design has a severe, documented consequence: any user sharing an egress IP with one or more other OpenCode users inherits their consumed quota before sending a single message.
This means a free user can open OpenCode for the first time in a day, send one trivial prompt, and immediately receive:
Free usage exceeded, subscribe to Go [retrying in 20h ...]
…despite having made zero prior requests that day. This is not a rate-limit burst or a runaway agent loop—it is the quota bucket already being empty when the user arrives, because someone else on the same IP spent it.
The problem is widespread, affects VPN users, CGNAT/mobile-carrier users, corporate NAT users, and cloud-hosted users, and is compounded by additional IP-bucketing side effects (paid users still hitting free limits, escalating retry timers, and undocumented reset semantics).
1. What “IP bucketing” means and how it is confirmed
“IP bucketing” means the free-tier usage counter is keyed to the egress IP address of the request, not to the API key, session ID, or account.
Confirmation from multiple sources:
- OmniRoute documentation repeatedly states: “The opencode free tier is IP-based, not account-based (max_completion_tokens needs to be <= max_tokens - prompt_len #9611). When connections share an egress IP they share one bucket.”
- The same wording appears across OmniRoute commits and issues: “The OpenCode free tier counts usage per egress IP, not per account (max_completion_tokens needs to be <= max_tokens - prompt_len #9611). Connections leaving through the same IP share one budget.”
- The OmniRoute issue feat(app): improved layout #10880 reiterates: “Some providers bucket their free-tier quota by egress IP — the maintainer himself documented this for opencode in max_completion_tokens needs to be <= max_tokens - prompt_len #9611 (‘the opencode free tier is IP-based, not account-based’).”
- OpenCode’s own schema uses an
IpRateLimitTablewithdailyCountandlifetimeCountkeyed by IP, and a source snippet shows the limiter throwingFreeUsageLimitErrorwhendailyCount >= dailyLimitfor that IP. - The
opencode-unlimited-proxyproject explicitly describes its rotating-IP pool as working around “the per-address quota ceiling”—separate from the refusal error itself. - Multiple community reports confirm the practical consequence: “Rate limits on free models are tied to IP address. Connecting to a VPN when you hit a limit resets it immediately.” Another: “换账户/换电脑没用,免费额度是按 IP 限制的” (“changing account/computer does nothing; the free quota is IP-limited”).
Reset timing is also IP-scoped. One source describes the free tier as allowing “200 requests a day, per exit IP, and resets at 00:00 UTC.” Another states the daily counter resets at UTC midnight (08:00 Beijing time) and is “与使用者的首次请求时间无关” (“unrelated to the user’s first request time”).
2. The core failure mode: pre-exhausted shared IP
Because the bucket is shared per IP, the state of your quota depends entirely on what other people on your IP have already done today.
2a. First-request failure (the primary symptom)
Issue #42128 documents exactly this: a user sends their first and only message (a minimal test prompt like “hai”) on DeepSeek V4 Flash Free and immediately receives Free usage exceeded on attempt #2. The issue’s “Possible Causes” section correctly identifies the mechanism: “Free-tier quota may be shared/pooled across all users and already exhausted server-side (not user-specific).”
Issue #35568 reports the same pattern: creating a brand-new OpenCode user immediately returns Free usage exceeded despite “no real consumption.”
A Chinese-language report states the conclusion directly: “换账户/换电脑没用,免费额度是按 IP 限制的,这也是为什么你新账户几分钟后照样报同样错误” — “Switching accounts or computers does nothing; the free quota is IP-limited, which is why your new account reports the same error within minutes.”
2b. Who is affected
| Network type | Why the bucket arrives pre-spent |
|---|---|
| VPN / commercial proxy | Hundreds or thousands of users egress through the same VPN endpoint IPs. OpenCode free traffic from any of them counts against the same bucket. |
| CGNAT (mobile carriers, ISPs) | Carrier-grade NAT puts many subscribers behind a small pool of public IPs. One heavy OpenCode user on the same carrier NAT can exhaust the bucket for unrelated users. |
| Corporate / campus NAT | Entire offices or campuses share a small number of egress IPs. A single developer burning tokens can lock out colleagues who have not even opened OpenCode that day. |
| Cloud-hosted environments (Railway, VPS, CI) | Cloud provider IPs are shared across many tenants. Railway’s deployment guide explicitly warns: “OpenCode’s free models need no key, but they are rate-limited per network address and Railway’s addresses are shared. If you see ‘Free usage limit reached,’ connect a provider.” |
| Datacenter / VPS IPs | These IPs are often already flagged or have a lifetime counter that is far advanced, because they are shared across many anonymous free-tier users. |
2c. The “unlimited usage exploit” confirms the inverse
Issue #34344 (“Unlimited usage Exploit on opencode models”) is the mirror image of the same design flaw: “Rate limits on free models are tied to IP address. Connecting to a VPN when you hit a limit resets it immediately… Deepseek v4 Flash and mimo v2.5 ran continuously for weeks with no effective limit.”
The existence of this exploit is strong corroboration: if quota were account-bucketed, rotating VPNs would not reset it. The fact that it does reset proves the bucket is IP-scoped. The same mechanism that lets a malicious user evade limits is the one that lets innocent users be blocked before they start.
3. Secondary effects of IP bucketing
3a. Paid users still hit the free-tier IP limiter
Issue #33318 reports that after adding a $20 paid Zen balance, requests still return FreeUsageLimitError from the IP rate limiter. The issue hypothesizes that “the free usage limiter may still be checked before billing/account state, or the request is being classified as free/anonymous even when using a paid Zen API key.”
This means IP bucketing does not cleanly yield to paid status. A paid user on a congested/shared IP can be blocked by the free-tier quota of that IP. OmniRoute code comments also note that a plan-level 429 “cascaded ‘Shared egress IP quota exhausted’ across ALL sibling connections for days.”
3b. Retry timers escalate and do not reflect the real reset
Users report that after hitting the IP limit, switching to a different free model does not bypass the block—instead the retry countdown gets longer. Issue #50093: “Use a free Zen model until quota is hit → Wait for the stated retry window → Retry with either same model still blocked or switch models and hit the same error with a longer countdown.”
This is consistent with IP bucketing: all free models share the same IP bucket, so switching models does nothing, and retry attempts can compound the cooldown.
3c. Reset semantics are opaque and inconsistent
Sources conflict on the reset window:
- Some say UTC midnight (00:00 UTC): “200 requests a day, per exit IP, and resets at 00:00 UTC.”
- Some say a rolling 24-hour cycle, not a midnight boundary: Issue What are the specific use terms of OpenCode Zen & its models per IP address, please? #16844 asks “when the usage counter shifted from a midnight reset to a 24-hour cycle.”
- Some Chinese guides say local-time midnight: “按 IP 每天本地时间 0 点重置” (“resets per IP at local time 0:00 each day”).
- Others say Beijing time 08:00 (UTC midnight).
The inconsistency itself is a bug: users cannot predict when their IP bucket will refill, and documentation does not resolve it.
3d. Privacy and fairness implications
IP bucketing means usage is attributed to a network address, not a person. A user who has never used OpenCode can be blocked because a stranger on the same ISP NAT, VPN, or corporate network consumed the shared allowance. There is no way for an individual to prove they have not used the quota, because the counter is not tied to their identity.
4. Why this is distinct from other quota issues
| Issue | What it covers | Why it does not explain first-request failure |
|---|---|---|
| Runaway agent loops (text narration, tool-call loops) | Token burn after the model starts responding | The first-request failures in #42128 occur before the model produces any output; the limit is already exceeded on entry. |
Compaction loops (total=0) |
Repeated context overflow during long sessions | A brand-new session with one tiny prompt never reaches compaction; the quota is already gone at the IP level. |
| Silent quota exhaustion / missing warnings | UX gap: no live meter, no low-quota warning | A warning would help, but it cannot fix the fact that the shared bucket was already empty before the user arrived. |
| Rate-limit bursts / concurrency | Too many simultaneous requests | A single first request should not trigger a concurrency limit; the 429 here is quota_exhausted, not rate_limit_exceeded. |
This bug is specifically: the quota unit is the wrong entity (IP instead of account), so the quota can be pre-consumed by third parties before the user acts.
5. Proposed fixes (prioritized)
P0 — Stop blocking users who have not used the service
- Bucket quota by account/session identity, with IP as a secondary concurrency guard. The canonical fix is what issue #34344 already suggests: “Tie rate limits to an account, not the IP.” Use IP only to limit concurrent requests or short-window burst rate, not to define the daily allowance.
- If account-bucketing is impossible for anonymous/free users, provide an explicit “IP bucket status” endpoint or UI indicator showing whether the current egress IP’s daily quota is already exhausted, before the user sends a request. This does not fix fairness, but it prevents the surprise and allows the user to switch networks before wasting time.
P1 — Fix secondary effects
- Ensure paid status overrides IP-based free-tier rejection. Issue [URGENT] Zen paid balance still hits FreeUsageLimitError / daily free usage limit. #33318 should be resolved so that a valid paid balance or API key bypasses the IP free-quota check entirely. Billing state must be evaluated before the IP limiter.
- Stop escalating retry timers across models on the same IP. If switching models cannot help (because the bucket is IP-scoped), the UI should say so explicitly and show the IP bucket’s reset time, rather than presenting a longer countdown that implies the new model has its own independent quota.
- Document the actual reset rule unambiguously. State whether the daily counter resets at UTC midnight, a rolling 24-hour window, or local midnight, and whether the lifetime count is also IP-scoped. Issue What are the specific use terms of OpenCode Zen & its models per IP address, please? #16844 already asks for this documentation; it should be treated as a P1 correctness issue, not a nice-to-have.
P2 — Structural and fairness improvements
- Add a grace allowance for “first request of the day” per account. Even if the IP bucket is shared, a small per-account floor (e.g., 5–10 requests/day) would let a user confirm the service works and distinguish a personal quota issue from a shared-IP exhaustion issue. This is a harm-reduction measure, not a full fix.
- Expose the egress IP and its bucket state in logs/diagnostics. The OmniRoute code persists
egress_ipper request; OpenCode could expose the same inopencode statsor logs so users can self-diagnose whether they are on a congested IP. - Consider a hybrid model: per-account daily quota + per-IP concurrency/rate limit. This preserves abuse protection (rate limiting prevents a single IP from flooding the service) while removing the pre-exhaustion failure mode (daily allowance is not consumed by unrelated users).
6. Immediate workarounds for affected users
| Workaround | How it helps | Limitations |
|---|---|---|
| Switch to a different egress IP (disable VPN, use mobile hotspot, reconnect PPPoE to get a new ISP IP) | The new IP has its own bucket, which may be unspent. Community reports confirm “换个人少用的 ip 就好了” (“switch to a less-used IP and it works”). | Requires control over the network path; not possible on corporate NAT or some CGNAT. |
| Avoid VPNs and proxies when using the free tier | VPN endpoints are the most commonly shared and pre-exhausted IPs. | Conflicts with privacy needs; not always possible. |
| Check the IP’s status before starting work | If the IP is already exhausted, you learn immediately instead of after preparing a prompt. | No official endpoint exists; requires manual testing with a trivial prompt. |
| Use a paid provider or your own API key | Bypasses the IP-bucketed free tier entirely. | Costs money; not a “fix” for the free tier. |
| Use a rotating-IP proxy only if you understand the trade-offs | Rotating pools can find an unspent IP. | The free tier also detects abusive rotation (403 FreeTierError); projects like opencode-unlimited-proxy and lingling exist but are not official solutions and have their own risks. |
7. Open questions for maintainers
- Is the free-tier daily quota definitively IP-bucketed, and if so, what is the exact bucket definition? (Single IPv4? IPv6 /64? NAT pool? Any account component at all?)
- What is the reset rule for the IP bucket—UTC midnight, local midnight, or rolling 24 hours? Why do sources and error messages conflict?
- Why does a paid Zen balance still hit
FreeUsageLimitError([URGENT] Zen paid balance still hits FreeUsageLimitError / daily free usage limit. #33318)? Is the IP limiter evaluated before billing state? Can this be reordered? - Is the lifetime counter also IP-scoped? One source states “lifetime 计数与当日计数都按 IP 记录” (“both lifetime count and daily count are recorded per IP”). If so, a long-lived shared IP can be permanently degraded even after a daily reset.
- What is the intended behavior for users on CGNAT, corporate NAT, or cloud platforms where a dedicated IP is impossible? Is the free tier simply unsupported on these networks, and if so, can that be stated explicitly?
- Can the retry mechanism be changed so that a 429 on one free model does not escalate the countdown for other models when they share the same IP bucket?
Plugins
No response
OpenCode version
No response
Steps to reproduce
No response
Screenshot
No response
Operating System
No response
Terminal
No response
Activity
opencodejr commentedon Nov 1, 2026
Thanks for the detailed write-up, @WinterGate-IC. I think this is a duplicate of #59387, the main issue for free tier problems. Feedback about how the free tier limit is counted, including limiting by IP address on shared networks, is collected there. The limit is enforced by the hosted OpenCode Zen service, not by the OpenCode app itself.
Possible duplicates and closely related issues, ordered from most to least likely:
Free usage exceededon the very first requestFreeUsageLimitErrorIf this happens to you on OpenCode v2, please add your version (
opencode --version), platform, model and the exact error to #59387. The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different.WinterGate-IC commentedon Nov 1, 2026
Fair enough — I'll accept the duplicate classification for the root cause (IP-bucketed free-tier quota). I've read #59387, #59781, #59858, #59257 and #42128, and I agree they describe the same mechanism.
Two things I'd like to flag before this closes, because I don't think the duplicate list fully covers them, and I don't want to lose them silently in a closed thread.
1. The remedy was closed, but the root cause wasn't fixed
#59781 was a concrete proposal: use per-account limits instead of IP-only limits so people on CGNAT / mobile / VPN / corporate NAT aren't blocked. It was closed as a duplicate of #59387 — a bug report thread. That's fine for deduplicating reports, but it means the feature request now has no open home. When a feature request is closed as a duplicate of a bug thread, it's easy for the requested change to never be explicitly accepted or explicitly declined.
Could a maintainer state the decision on the record, one way or the other?
Right now a user reading #59387 can't tell which of those is true.
2. "Enforced by hosted Zen, not the app" changes where this needs to go
You noted the limit is enforced by the hosted OpenCode Zen service, not the OpenCode app. That's the key detail, and it creates a process gap:
Concrete ask: please link #59387 to the Zen-side owner or tracker, or tell us where to file it so it reaches the service team. Happy to re-file there instead — I just don't want to file the same thing twice in the wrong place.
3. Sub-items in my report that I couldn't match to the duplicate list
Not asking to keep this issue open for these — just asking which of these should be separate issues vs. comments in #59387:
FreeUsageLimitError([URGENT] Zen paid balance still hits FreeUsageLimitError / daily free usage limit. #33318). This is listed as "[URGENT]" but I can't tell if it's open, fixed, or also folded into Free tier issues: read this first, then post your report here #59387. If a paid user can be blocked by another tenant's IP bucket, that's a billing-correctness bug, not a free-tier UX issue, and arguably shouldn't live in a free-tier thread.GET /quotastyle check, or showing bucket state inopencode stats, would convert a 24-hour surprise into a 2-second check. This is a UX fix that's valid even if IP bucketing is kept.If the answer to all four is "comment on #59387," say so and I'll do exactly that.
What I'll do next
Unless you tell me otherwise, I'll post a single consolidated comment to #59387 containing: the evidence that the bucket is IP-scoped (#9611 + the
IpRateLimitTablereference + the VPN-reset corroboration in #34344), the list of affected network types, and the four sub-items above — so maintainers have one artifact instead of re-reading five closed duplicates. I'll includeopencode --version, platform, model and the exact error text as requested.I'm not trying to keep this open for its own sake. I just want the decision recorded somewhere, since right now the root cause, the feature request, and the paid-user bug are all pointing at each other in a circle.
opencodejr commentedon Nov 2, 2026
Hi @WinterGate-IC, we haven't heard back, so we're closing this issue as a duplicate of #59387, the main issue for free tier problems. Feedback about the free tier limit being counted per IP address, including on shared NAT, CGNAT, VPN and cloud networks, is collected there. Earlier reports of this, #59781 and #59858, were closed in favour of it too.
If you hit this on OpenCode v2, please add your version (
opencode --version), platform, model and the exact error to #59387. If your problem turns out to be different, reply here and explain how, and we can reopen this.