Repository navigation
Conversation
…egress (decolua#4983) The free-tier gate rejects the request when its egress IP is flagged, but the transport itself succeeds - so the existing proxy-failure fallback never fires and every retry keeps leaving through the same flagged proxy pool. Live measurement, identical body and headers in the same minute: flagged pool egress -> 403 FreeTierError, direct egress -> 200. proxyAwareFetch now inspects a proxied 403 and, when the body carries the FreeTierError marker, replays the request once on the direct egress. strictProxy keeps its contract (never leaves the configured path), a body already consumed cannot be resent, and a failed direct replay surfaces the original 403. Non-gate 403s and streaming 2xx responses pass through untouched.
Withdrawn — correction after follow-up measurements (2026-10-20)I withdraw my endorsement of #4987 as a root transport fix and the claim that this establishes a complementary DNS-plus-fallback solution. This correction concerns my comment only, not the author’s independent #4988 proposal. Please do not treat the original text below as a current fix recommendation. I have withdrawn and am closing my local-DNS proposal #4987. Follow-up measurements in this AdGuard setup showed a material side effect: with local DNS / numeric SOCKS targets, multiple pools could expose the same service-visible US egress; proxy-side DNS / domain targets exposed their respective distinct VPN egresses. We removed our locally hardcoded OpenCode DNS override and restored upstream 9router's original transport. The optional PR and the local hardcoded override were not the same implementation. Earlier success/403 differences were time- and path-specific. They do not prove an upstream 9router DNS defect, a universal OpenCode fix, a Cloudflare WAF or load-shedding rule, or that our DNS override caused the IP-abuse flags. The exact server-side trigger and expiry remain unverified. Sorry for the earlier overstatement. See #4987 for the full correction. No credentials or egress addresses are published here. Superseded original comment — retained as history, not a current recommendationThanks for puting this together @yxxrn! This direct fallback is a great safety net when a proxy's egress IP genuinely loses its reputation or gets flagged by Cloudflare WAF. I wanted to share our complementary findings from #4983 and #4987, as both PRs actually fit together really well: Complementary findings on SOCKS proxies (#4987)During our controlled tests on #4983 across 16 active SOCKS5 proxy pools, we found that many proxy rejections weren't caused by a permanently blacklisted proxy IP, but by SOCKS domain target addressing (remote DNS / ATYP 3):
How #4987 and #4988 complement each other
Having both—a proper transport option so proxies can succeed (#4987), plus a direct replay fallback when an egress is truly burnt (#4988)—gives users the best of both worlds. |
…t normalization, 403 direct replay - internal/proxy/opencode.go: TranslateOpenCodeSessionID now generates fresh timestamped session IDs instead of hash-derived ones. The free-tier gate rejects non-recent timestamps with 403 FreeTierError. - internal/translator/opencode_v2_fingerprint.go: port decolua/9router#4972 - normalize colliding fingerprint tool definitions in-place when arguments are compatible, instead of always aliasing to *_client. - internal/proxy/proxy.go: port decolua/9router#4988 - replay proxied 403 FreeTierError once on direct egress (unless strictProxy). - internal/providers/capabilities.go: context window 1M for muse-spark and nemotron free models.
Merging decolua#4987 + decolua#4988 + decolua#4984 + decolua#4991 for opencode free tier error fix
… strategy in full Problem: the fork ran an older fingerprint contract than upstream frontier on the keyed lane and lacked both proxy escape hatches from issue decolua#4983. opencode-zen still sent the v1 UA plus a stub bash/glob/grep/read quartet with empty schemas and the v1-only x-opencode-request header, the fingerprint helper could not satisfy the gate when a caller tool reuses read/shell with platform-specific wording, and proxyAwareFetch had no way out of an egress the gate had flagged. Changes: port upstream PR decolua#4972 (in-place fingerprint normalization with alias fallback), decolua#4883 (UA opencode/latest/2.0.29/cli across both executors, registry and dashboard test; strict v2 channel matcher kept), decolua#4860 fingerprint/strategy for opencode-zen (verbatim read/shell via applyFingerprintTools, cli client, four session mirrors, drop x-opencode-request and x-api-key for free tier; /zen/v1 to /inference/v1 switch NOT taken), decolua#4987 (open-sse/utils/proxyDispatcher.js with opt-in socks5 dns=local, shared by proxyFetch and the dashboard proxy test, fail-closed ERR_PROXY_DNS_MODE) and decolua#4988 (replay a proxied 403 FreeTierError once on the direct egress, strictProxy contract preserved). undici ^7.19.2 to ^7.30.1. Version 0.5.108. Verification: full suite re-derived on this machine from a git stash -u baseline - failure sets byte-identical (95 = 95, 0 new), passes 3592 to 3617. Targeted opencode/proxy/session groups 214 pass. undici 7.30.1 exposes Socks5ProxyAgent and createSocks5Connection; the local-DNS dispatcher is exercised end to end against a local SOCKS5 plus TLS fixture (11 cases). Root cause of the free-tier 403 is egress-IP load-shedding (decolua#4983), so these ports restore request truth and add escape hatches but cannot make a flagged egress succeed.
feat(opencode): integrate upstream decolua#4988 free-tier direct replay; drop 429 rotation
luqman-v1
left a comment
There was a problem hiding this comment.
LGTM: replays OpenCode 403 FreeTierError once on direct egress when the proxy IP is flagged, while properly respecting strictProxy configurations. Safe and well tested.
…th security hardening (#67) * fix(translator): downgrade deepseek json_schema to json_object Ports decolua/9router#5150 DeepSeek documents only json_object; json_schema returns HTTP 400 "This response_format type is unavailable now". Downgrade json_schema to json_object so structured requests continue in JSON mode rather than hard-failing. Optional chaining guarantees non-object response_format values cannot throw. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(capabilities): cap oc/deepseek-v4.1-flash-free context window Ports decolua/9router#5179 The OpenCode free backend rejects requests above ~330k prompt tokens (upstream issue #5097). While upstream previously matched a generic 1M pattern and capped it to 330000, KhatamRouter already safe-listed this model at 200000/32000 based on models.dev. Explicitly pin the entry in PROVIDER_CAPABILITIES.opencode at 200000/32000 to ensure contextWindow <= 330000 and maxOutput <= contextWindow, preserving vision and reasoning without raising the advertised token window beyond our safe baseline. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(providers): avoid network probe on opencode free tier connection test Ports decolua/9router#5193 The OpenCode free lane has no secret to validate (it uses "Bearer public"), and the previous probe sent an outdated "opencode/1.18.x" user-agent fingerprint to https://opencode.ai/zen/v1/models which rejects with 403 FreeTierError. Return valid: true directly to prevent spurious failures and unnecessary network egress, while keeping the opencode-go authenticated probe untouched. Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(opencode): port session timestamps and CLI fingerprint (#5222) Ports decolua/9router#5222: - Mint translated OpenCode session IDs with Date.now() and memoize per (clientTool, sessionId) with bounded Map and MEMORY_CONFIG TTL cleanup. - Bump default OpenCode User-Agent to opencode/latest/2.1.4/cli. - Accept shell-description platform wordings (linux/darwin/win32) as valid fingerprint definitions. - Apply <env> environment marker (applyEnvFingerprint) across Responses, Chat Completions, and Claude body shapes (with fail-open and idempotency). - Support Claude-shaped body fingerprint placement for messages models (union-alpha) via top-level system field. Security deviation from upstream: Upstream dynamically reads process.cwd() and os.tmpdir() to construct the <env> block, disclosing the gateway host's filesystem layout, username, and directory structure to external upstreams on every request. KhatamRouter substitutes fixed neutral constants (/home/user/project, /var/cache/opencode, "linux") that satisfy the upstream gate while preventing host reconnaissance. Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(proxy): replay proxied 403 FreeTierError once on direct egress Ports decolua/9router#4988. When an OpenCode free-tier request receives a 403 FreeTierError because the proxy's egress IP has been flagged by the gate, replay the request once on direct egress. Hardened with the following security deviations from upstream: - Restrict replay exclusively to OpenCode host (opencode.ai and subdomains). - Honour strictProxy (and direct fallback prevention) without exception. - Cap buffered body inspection at 64 KiB to prevent DoS via unbounded buffering. - Replay at most once with identical method/body/headers, logging only the host. - Reject replay for consumed/locked stream request bodies and return rebuilt response. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(upstream): record pre-Wave-8 Decolua 9router ports and audit ledger Ports decolua/9router#4988, decolua/9router#5222, decolua/9router#5150, decolua/9router#5179, and decolua/9router#5193: - Document what was ported across all 5 PRs into docs/UPSTREAM-PORTS.md - Record all security deviations and hardening invariants in docs/UPSTREAM-PORTS.md - Add pre-Wave-8 upstream port section to CHANGELOG.md Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
Problem
The OpenCode free-tier gate answers
403 FreeTierErrorwhen the request exits from an IP it has flagged. The transport itself succeeds, so the existing proxy-failure fallback inproxyAwareFetchnever fires and every retry keeps leaving through the same flagged proxy pool — the lane stays 403 until a human rebinds the pool in the dashboard.This is the failure mode users are hitting right now in #4983 (and the flip pattern reported in #4845): a configured pool whose egress IPs lose their reputation turns
oc/*hard-403 with no automatic escape, while the identical request on the direct egress works.Live measurement (same body, same headers, same minute, same machine)
zone-isp_proxy3), 3 runszone-isp_proxy2, the one 9router-go uses), 3 runsread/shellfingerprint toolsBecause the 403 arrives on a successful proxied response, the current
catch-based fallback (Proxy failed, falling back to direct) is structurally blind to it.Change
open-sse/utils/proxyFetch.js:FreeTierErrormarker. On a match, replay the request once on the direct egress (fetchWithTlsFallback(url, options, null)).strictProxy: truekeeps its contract: the proxied response is returned as-is, never a replay (checked before the body is even consumed).status === 403.Scope is intentionally narrow: the marker is OpenCode-specific, so no other provider can trigger the replay.
Verification
New
tests/unit/opencode-freetier-direct-replay.test.js(5 cases):403 FreeTierError→ one direct replay → returns its 200 (asserts call order: dispatcher present → absent).strictProxy: true→ the 403 is returned, one fetch call, no replay.Results:
strict-proxy-enforcement,qoder-proxy-replay,cursor-agent-strict-proxy): identical 6 failed / 8 passed with and without the patch (those failures are the pre-existing environment/red-test baseline documented in CLAUDE.md — missing root deps such asuuid), i.e. 0 pass→fail.Security
No header manipulation, no credential or token handling, no new network targets: the replay goes to the same URL the caller requested, over the direct egress path that
proxyAwareFetchalready falls back to for transport failures.strictProxystays fail-closed.Refs #4983.