Why OpenCode Is a Multi-Leg Terminal Client
OpenCode looks like one binary, but operational reality is closer to a small distributed system. The TUI may call provider HTTP APIs for chat completions, list and refresh models, download LSP helpers unless disabled, talk to configured MCP servers, and occasionally open a browser for OAuth or token exchange. Official docs describe opencode auth login against the models.dev provider catalog, storage in ~/.local/share/opencode/auth.json, environment overrides—including OPENCODE_MODELS_URL for a custom catalog endpoint—and companion modes such as opencode serve, opencode web, and opencode attach when you split UI from backend.
Clash evaluates every TCP and QUIC decision independently. That precision is powerful until your YAML expresses several incompatible truths about “OpenCode traffic.” One narrow rule sends api.anthropic.com to a low-latency exit, another leaves models.dev on DIRECT because it looked generic, and an aggressive geography line swallows a documentation hostname before your explicit suffix entry runs. The terminal CLI does not print a routing diagram; it retries, backs off, and eventually surfaces an API timeout string that sends people toward the wrong diagnosis.
The remedy is narrative coherence: catalog, documentation, share or marketing surfaces, each model API family you invoke, and any OAuth leg should share one deliberate outbound envelope during the test window. The habits in Clash rule routing best practices still apply—explicit vendor stanzas above lazy MATCH behavior, and treating every remote rule-provider refresh as a config change that can reorder outcomes overnight.
Typical Timeouts: Catalog, Provider APIs, and OAuth Drift
Support patterns repeat. First, opencode models --refresh appears to hang: models.dev or your custom OPENCODE_MODELS_URL host crosses a different path than the provider you pick minutes later, so the CLI’s idea of “available models” disagrees with transport reality. Second, login succeeds in principle yet tokens never stick because a follow-up account or consent hostname exited through another region. Third, inference calls stall while short health checks look fine—streaming completions keep connections open long enough for idle timers, lossy exits, or asymmetric paths to surface as generic client timeouts.
Fourth, MCP mcp auth opens OAuth to servers you added manually; if only the MCP hostname is missing from your split routing plan, tools fail mysteriously while base chat still works. Fifth, the mundane case: developers export HTTPS_PROXY and assume universality, yet a subprocess uses a TLS stack that bypasses the variable—Clash stays idle in the UI even though the shell “has a proxy.” When someone says “OpenCode is down,” ask which hostname stalled; user-visible strings rarely distinguish DNS lies from middlebox interference.
Hostnames and Flows You Should Expect
You do not need encyclopedic subdomain coverage; you need live captures during failures. Expect documentation and marketing surfaces on opencode.ai and related help domains, model catalogs on models.dev, and share imports documented against opncd.ai hosts. Provider traffic follows whichever vendors you enabled: Anthropic, OpenAI, Google, and others each bring their own API and OAuth families—reuse the suffix vocabulary from Anthropic split routing, OpenAI / ChatGPT routing, and Gemini / Google AI routing rather than reinventing lists from memory.
Automation features that run through GitHub add github.com, api.github.com, and related Git asset or Actions hostnames; align those with any existing Git or Copilot-oriented coverage such as GitHub Copilot and VS Code marketplace split routing so PR fetchers and comment bots do not oscillate between exits. Plugin installs or upgrades may hit package registries—treat unfamiliar npm or Git download traffic as part of the same incident until disproven.
models.dev, OPENCODE_MODELS_URL, and Refresh Surprises
OpenCode’s provider list defaults to models.dev data. That means the CLI’s startup path and opencode models --refresh are not decorative; they decide which model IDs appear and how authentication is wired before your first real prompt. If that catalog fetch rides DIRECT through a congested or filtered path while actual inference rides a proxy exit with a different regional perspective, you can watch “model not found” or stalled refresh loops that feel like vendor outages.
Teams that pin catalogs with OPENCODE_MODELS_URL inherit a new obligation: that custom hostname must join the same policy group as downstream model API calls, and DNS for the custom host must align with the resolver mode you chose in Clash. When experimenting, log the exact URL, capture TLS failures separately from HTTP 403 semantics, and document rollback steps—custom endpoints are high leverage and high confusion.
Terminal Capture: Proxy Env Vars versus Ignored Stacks
The shortest workable recipe is still exporting HTTPS_PROXY=http://127.0.0.1:7890 toward your mixed port—the port is illustrative. That remains reliable only when every leg honors it. OpenCode’s multi-process design means some children may reset the environment, pin HTTP/3 where local forwarders disagree subtly, or use libraries that consult NO_PROXY in surprising ways. When that happens, the terminal CLI looks broken while Clash appears idle because the stalled PID never consulted your proxy at all.
Document the matrix: HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY with explicit loopback and RFC1918 exemptions for local attach or serve workflows. Pair exports with live connection evidence—prove the OpenCode PID opened SYN through your mixed port. For system-wide enforcement, TUN captures traffic environment variables miss, at the cost of OS prompts and VPN stacking puzzles documented in Clash on macOS: TUN versus system proxy and the TUN deep dive.
Containers repeat the story with different IP namespaces. If you run OpenCode adjacent to Docker workloads, re-read Clash with Docker and CLI mixed ports so host forwarding and guest env vars describe one network instead of three competing stories.
OAuth, Loopback, and MCP mcp auth
Browser-based OAuth for developer tools routinely ends on http://127.0.0.1: listeners. Your proxy chain must not elongate or rewrite that loopback path. Accidental recursion—localhost proxied through a remote exit, or a debugging MITM stacked on top of Clash—shows up as “login never finishes” even when provider status pages are green.
opencode mcp auth extends OAuth to MCP servers you trust. Those third-party domains belong in the same incident folder as core vendors: partial coverage produces tool failures that look like flaky agents rather than network split routing. When debugging, run opencode mcp debug for OAuth issues after you verify plain provider calls, then align MCP host suffixes with the same outbound group you use for interactive chat during the test window.
GitHub Agent Traffic When You Use opencode github
Repository automation paths call GitHub APIs and ship workflows; latency there is not “AI hype,” it is gating work. Keep Git operations, issue comment endpoints, and artifact downloads on a predictable exit that matches how you browse GitHub in the browser during the same session. If corporate inspection or regional routing handles Git differently from your model vendor, you will see Actions that start late or PR helpers that stall—not because models regressed, but because the automation leg hit a stricter path than your TUI.
One Coherent Policy Story per Working Session
Name a dedicated group—OPENCODE_CLI is descriptive—and route OpenCode infrastructure hostnames there together: at minimum documented opencode.ai surfaces, models.dev, observed opncd.ai share links, and any custom catalog domain. Then stack provider-specific groups or suffix lists that mirror the models you actually invoke so Anthropic, OpenAI, or Google traffic follows one envelope per vendor during the incident window.
Ordering discipline still matters: domestic direct lines, RFC1918 bypass, and enterprise VPN prefixes should precede vendor catches so you never leak LAN address space through an unintended public exit. If evaluating clients, read choosing the right Clash client with logging clarity in mind—AI programming agent failures reward telemetry you can screenshot.
Illustrative DOMAIN-SUFFIX Baseline
The fragment below is illustrative, not canonical law. Subscriptions duplicate suffixes; compliance may prohibit certain exits; your capture will always beat a blog YAML snippet. Diff against your live profile and promote temporary DOMAIN entries only after repeated sightings across versions.
Illustrative rules fragment
rules:
- DOMAIN-SUFFIX,opencode.ai,OPENCODE_CLI
- DOMAIN-SUFFIX,models.dev,OPENCODE_CLI
- DOMAIN-SUFFIX,opncd.ai,OPENCODE_CLI
- DOMAIN-SUFFIX,github.com,OPENCODE_CLI
- GEOIP,CN,DIRECT
- MATCH,DIRECT
Intent: documentation and product surfaces, catalogs, share links, and Git automation move together before broad geography rules. Replace OPENCODE_CLI with your real policy group name, insert explicit Anthropic, OpenAI, or Google suffix stanzas above lazy MATCH behavior, and keep keyword sprays like DOMAIN-KEYWORD,ai out of production configs—they over-capture and rot quickly on teams that actually read YAML during outages.
Layering Anthropic, OpenAI, and Google Families
OpenCode is vendor-agnostic by design; your Clash profile should reflect the vendors you selected in opencode auth login. Rather than duplicating full suffix encyclopedias here, reuse the focused articles already tuned for each ecosystem: Anthropic paths in Claude / Anthropic split routing, OpenAI stacks in OpenAI Codex CLI CDN routing and ChatGPT web routing, Google stacks in Gemini CLI routing.
The architectural invariant is simpler than memorizing every subdomain: pick one outbound group per vendor per session, place suffix lines before zealous geography catches, and refuse to “fix” model timeouts by adding a twentieth keyword rule that nobody can explain next quarter.
Rule Providers, Ordering, and Midnight Surprises
Remote rule sets help you track moving endpoints—until downloading updates itself loops through a broken chain and lists silently age. Watch refresh logs, keep a minimal owned baseline you can paste offline, and diff provider updates when fresh API timeout chatter arrives the same morning your rules auto-refreshed.
Hygiene lists occasionally misclassify shared infrastructure. If behavior flips immediately after an update, reproduce with the suspect provider disabled on a scratch machine, confirm the interaction, and document the finding—your future self should inherit a ticket note, not folklore.
DNS, fake-ip, TUN, and Resolver Multiplexing
Misaligned DNS is the quiet accomplice of bad split routing. macOS encrypted DNS, browser DoH, corporate split-horizon, and Clash DNS each believe they own name resolution. When fake-ip mapping disagrees with the outbound you ultimately select, you see “instant resolve, endless dial,” which surfaces to users as unhelpful terminal CLI timeouts with no nouns attached.
Meta-class cores expose rich DNS controls; study Clash Meta DNS: nameserver fallback and fake-ip filter until the relationship between nameserver policy, fallback, and filter lists feels pedestrian. For a developer-login angle parallel to OpenCode, see Cursor login and AI timeouts—different product skin, same lesson about OAuth plus API legs drifting apart behind a proxy.
When multiple resolvers fight, pick a single orchestrator for the isolated test, record that decision, and revert deliberately. Multiplexing DNS without a diagram is how teams donate weeks to ghosts.
Reading Logs When the CLI Only Shows a Spinner
Treat logs like a narrow sequence diagram. SYN stalls usually mean routing or DNS lies; TLS hangs after ClientHello often mean exit mismatch or middlebox meddling; steady throughput followed by connection resets may point at brittle nodes rather than ignorant YAML. Bucket hostnames mentally into catalog, vendor API, OAuth, MCP, Git automation, and static CDN-shaped edges; when one bucket diverges from your intended policy consistently, fix the rule—not the model label in your last prompt.
Pair this mindset with connection logs: timeout and TLS patterns so vocabulary stays consistent across incidents. Enable verbose OpenCode logging only where docs recommend it, and redact tokens before sharing captures.
Verification Checklist Before You Blame the Model
- Confirm you are permitted to use Clash and OpenCode with your chosen vendors on this network.
- Verify negligible clock skew and pause HTTPS interception while testing.
- Reproduce once with logging open; copy exact hostnames overlapping the stall window.
- Check each hostname against your effective ruleset—did catalog, OAuth, MCP, and API calls hit the intended groups?
- Audit rule order for geography catches, tracker lists, or keyword starvation above suffix coverage.
- Align DNS mode with fake-ip and fallback; hunt for instant resolve without successful TCP completion.
- Validate
NO_PROXYfor loopback callbacks; remove surprise double proxies. - If env vars look ignored, test TUN after resolving VPN stacking conflicts.
- Confirm remote providers refresh successfully—no silent stale lists.
- Only after local variables are eliminated, rotate nodes or consult vendor status pages—not before.
Write timestamps, config diffs, and hypotheses. War rooms cost less when the next engineer inherits evidence instead of vibes.
Frequently Asked Questions
Does disabling remote model fetch fix timeouts?
OPENCODE_DISABLE_MODELS_FETCH stops remote catalog retrieval, which can make startup predictable in air-gapped experiments but does not replace provider routing—you still need coherent paths to whichever model APIs remain enabled. Use it as a controlled experiment, not a permanent patch for sloppy YAML.
Why do only streaming responses fail?
Streaming completions keep sessions open far longer than quick REST probes. Middleboxes and lossy exits that tolerate short navigations may reset long AI streams. Inconsistent split routing exaggerates the symptom: one leg proxies while keepalive traffic bumps a different path with stricter idle timers.
Is OpenCode unique here?
The pattern generalizes across terminal AI coding agent tools: OAuth, moving model API endpoints, catalogs or CDNs, and optional automation integrations multiply legs until Clash policies disagree. Product names churn; the debugging story does not.
Wrap-Up: Observable Split Routing for the OpenCode CLI
One-click VPN wrappers can mask complexity, but they also mask evidence: when OpenCode fails, you get a quiet spinner instead of a rule-hit map. Global tunnel products often struggle with nuanced split routing—everything shares one exit, which sometimes “fixes” a stall while breaking domestic services you needed direct. Hand-curated hosts files rot weekly and seldom track moving CDN edges without operational drag.
Clash V.CORE keeps the upside of explicit policy: suffix rules you can read in code review, provider updates you can audit, DNS modes that line up with TUN or mixed ports, and logs that show whether models.dev, an Anthropic dial, or an MCP OAuth leg stalled first. That observability is the difference between shipping features with an AI programming agent and spending evenings guessing which hostname escaped your last YAML tweak.
→ Download Clash for free and route OpenCode catalogs, model API families, and OAuth helpers as one readable story—then get back to building instead of packet archaeology.