How OpenClaude Handles Different Transport Kinds: WebSocket, SSE, Hybrid, and Local

OpenClaude uses a unified Transport interface to abstract all model-server communication, with getTransportForUrl() in src/cli/transports/transportUtils.ts automatically selecting the appropriate implementation based on URL scheme, CLI flags, and provider configuration.

The transport layer is the critical bridge between OpenClaude's CLI and any language model backend—whether that's a local binary, an OpenAI-compatible API, or an Anthropic endpoint. Understanding how these different transport kinds work helps you debug connection issues, optimize for restrictive network environments, and extend the system with new providers.

The Transport Interface: One Contract, Many Backends

At the heart of OpenClaude's transport system is the Transport interface defined in src/cli/transports/Transport.ts. Every concrete transport implements the same methods:

  • connect() — establish the connection
  • write(data) — send a message to the model
  • close() — tear down gracefully
  • setOnData(callback), setOnConnect(callback), setOnError(callback) — event hooks

This abstraction means higher-level code in remoteIO and structuredIO never needs to know whether it's talking over WebSocket, HTTP POST, or a local process pipe.

The Five Transport Kinds in OpenClaude

WebSocket Transport (Default)

WebSocketTransport.ts provides full-duplex communication over ws:// or wss:// URLs. It's the default when you point OpenClaude at a WebSocket-capable server.

import { getTransportForUrl } from 'src/cli/transports/transportUtils.js';

const url = 'wss://api.example.com/v1/stream';
const transport = await getTransportForUrl(url);
await transport.connect();

The implementation handles reconnection with exponential back-off and keeps the connection alive for streaming responses.

Hybrid Transport: WebSocket Reads + HTTP POST Writes

HybridTransport.ts solves a common firewall problem: outbound WebSocket traffic blocked, but inbound streaming still possible. It uses WebSocket for reading server-sent events and plain HTTP POST for writing messages.

OpenClaude automatically falls back to Hybrid when it detects an http:// or https:// URL without explicit WebSocket markers. You can also force it with --transport=hybrid.

SSE Transport: Server-Sent Events for OpenAI-Compatible APIs

SSETransport.ts implements the CCR v2 "openai-compatible" protocol. It reads from an /sse endpoint and writes via HTTP POST. This is what powers connections to OpenAI's API, Groq, and other compatible providers.

const url = 'https://api.openai.com/v1/chat/completions';
const transport = await getTransportForUrl(url);
// Returns SSETransport instance

The transport parses SSE-formatted chunks and converts them to OpenClaude's internal StdoutMessage format.

Local Transport: In-Process Model Execution

When you run --local or configure a provider with "kind": "local", OpenClaude bypasses the network entirely. WorkerStateUploader.ts spawns a child process from your specified binary and communicates over stdio pipes.

import { getLocalTransport } from 'src/cli/transports/transportUtils.js';

const transport = await getLocalTransport({
  exec: './my-llama-binary',
  args: ['--ngl', '35'],
});

No ws:// or HTTP stack is involved. Messages flow directly to the spawned process, making this ideal for:

  • Running quantized models via llama.cpp
  • Dockerized local deployments
  • Custom compiled inference engines

CCR Client: Provider-Specific Wrappers

ccrClient.ts sits above the raw transports, providing the "openai-compatible" and "anthropic-compatible" transport kinds. It selects SSE or Hybrid based on provider configuration and CLI flags like --provider=openai or --provider=anthropic.

How Transport Selection Works

The factory function getTransportForUrl() in src/cli/transports/transportUtils.ts implements this decision logic:

  1. Parse URL scheme

    • ws:// / wss:// → WebSocketTransport
    • http:// / https:// with /sse path → SSETransport
    • http:// / https:// otherwise → HybridTransport
  2. Detect local execution

    • --local flag or "kind": "local" in config → WorkerStateUploader via getLocalTransport()
  3. Return initialized transport

    • All paths yield an object conforming to the Transport interface

Here's a runtime fallback example when WebSocket fails:

let transport = await getTransportForUrl(wsUrl);

try {
  await transport.connect();
} catch (err) {
  console.warn('WebSocket failed, falling back to HTTP Hybrid');
  transport = await getTransportForUrl(httpUrl);
  await transport.connect();
}

Why This Design Matters

  • Pluggable backends — New providers need only a URL scheme handler and optionally a CCR client wrapper
  • Automatic resilience — Network restrictions trigger graceful degradation without user intervention
  • Consistent developer experience — All transports expose identical methods; higher-level code stays portable

The CLI's diagnostic commands like --verbose and --transport-dump leverage the optional isConnectedStatus and transport metadata to show exactly which kind is active.

Key Source Files

File Purpose
src/cli/transports/Transport.ts Core interface all transports implement
src/cli/transports/WebSocketTransport.ts Default WebSocket transport
src/cli/transports/HybridTransport.ts WebSocket reads + HTTP POST writes
src/cli/transports/SSETransport.ts SSE reads for OpenAI-compatible endpoints
src/cli/transports/WorkerStateUploader.ts Local process communication
src/cli/transports/ccrClient.ts Provider-specific CCR wrappers
src/cli/transports/transportUtils.ts Factory for transport selection

Summary

  • OpenClaude's transport system centers on a single Transport interface in Transport.ts
  • getTransportForUrl() automates selection: WebSocket for wss://, SSE for /sse paths, Hybrid otherwise
  • Local execution uses WorkerStateUploader with stdio pipes—no network stack
  • Provider compatibility (OpenAI, Anthropic) is handled by ccrClient.ts selecting appropriate underlying transports
  • Automatic fallback between transport kinds enables operation across diverse network environments

Frequently Asked Questions

How do I force a specific transport kind?

Use the --transport CLI flag with values websocket, hybrid, sse, or local. For example: openclaude --transport=hybrid --url https://api.example.com. When omitted, OpenClaude infers from the URL scheme and provider configuration.

Why would Hybrid transport be chosen automatically?

Hybrid is selected when you specify an http:// or https:// URL without WebSocket indicators. This commonly occurs in corporate environments where firewalls block outbound WebSocket connections but still permit HTTP POST requests and server-sent event streams.

What's the difference between "local" transport and running a local HTTP server?

Local transport (--local or "kind": "local") runs the model as a child process and communicates via stdio pipes—no TCP stack, no port binding. Running a local HTTP server involves starting a separate service (like Ollama) and connecting to it via WebSocket or HTTP, which uses the standard network transports.

Can I implement a custom transport for my internal API?

Yes. Implement the Transport interface from src/cli/transports/Transport.ts and register it in transportUtils.ts by extending getTransportForUrl() with your URL pattern detection. Your transport will work with all existing CLI features without modifying higher-level code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →