Skip to content

fix(opencode): refresh the client fingerprint to the 2.0 channel form - #4883

Open
AppleVibe wants to merge 1 commit into
decolua:masterfrom
AppleVibe:fix/opencode-ua-fingerprint
Open

AppleVibe wants to merge 1 commit into
decolua:masterfrom
AppleVibe:fix/opencode-ua-fingerprint

Conversation

@AppleVibe

Copy link
Copy Markdown
Contributor

Fixes #4837, #4861

Problem

The free-tier gate on opencode.ai classifies requests by client fingerprint. The pinned User-Agent: opencode/1.18.31 no longer matches the official CLI — current builds send the channel form opencode/latest/<version>/<name>. Traffic with the stale UA is treated as an unidentified client and rejected with 403 FreeTierError / 429 FreeUsageLimitError on every free model (issue reports from 2026-10-13/14).

Additionally, hasValidOpencodeVersion only matched the legacy opencode/<semver> form, so when a downstream client already sent the correct channel-form UA, the executor silently replaced it with the stale pinned one.

Fix

  • Default User-Agent → opencode/latest/2.0.29/cli in the opencode and opencode-zen executors, both registry entries, and the connection-test utils (5 places).
  • Default x-opencode-client → cli (matches the official CLI attribution).
  • hasValidOpencodeVersion accepts both the legacy opencode/1.x.y form and the channel form opencode/<channel>/<semver>/<name>, so a valid downstream UA is forwarded as-is.

Not changed

Session/request id generation — verified against the upstream source (packages/schema/src/identifier.ts, Identifier.create descending, 6-byte time component + 14 base62 chars). 9router's implementation already matches, so it is left alone.

Test

tests/unit/opencode-session.test.js — 25/25 passing, including new expectations for the channel-form UA and the forwarded downstream UA.

Known limitation (out of scope)

Since 2026-10-10 the gate also inspects the request body (stream:true, ≥2 tools including one named bash) on the /responses lane. That is an upstream payload restriction, not a header issue — spoofing a bash tool entry is a policy question better discussed separately.

Security note

Only outgoing headers to opencode.ai change. No inbound auth, X-Forwarded-For, or SSRF surface touched.

🤖 Generated with Claude Code

The free-tier gate on opencode.ai validates the User-Agent and the
session-id format. The pinned "opencode/1.18.31" no longer matches
what the official CLI sends; current builds send the channel form
"opencode/latest/<version>/<name>", and traffic with the stale UA is
classified as an unidentified client and rejected with 403
FreeTierError / 429 FreeUsageLimitError (decolua#4837, decolua#4861).

- send opencode/latest/2.0.29/cli as the default User-Agent (executors
  opencode/opencode-zen, registry entries, connection test utils)
- default x-opencode-client: desktop → cli
- hasValidOpencodeVersion accepts both legacy opencode/<semver> and the
  channel form opencode/<channel>/<semver>/<name>, so a downstream
  client's valid UA is forwarded instead of silently replaced

Session/request id generation already matches the upstream
Identifier.create (descending, 6-byte time component) — verified
against packages/schema/src/identifier.ts, unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
andyvandaric added a commit to andyvandaric/9router that referenced this pull request Oct 17, 2026
… 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.

@luqman-v1 luqman-v1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM: updates OpenCode user-agent fingerprint to official 2.0 channel form while preserving backward-compatible version checks. Safe and clean.

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.

Today, OpenCode was updated again, and the previous custom request header is no longer valid

2 participants