How to Configure Session Affinity in OmniRoute for Claude Code on the Same Provider

To configure session affinity in OmniRoute for Claude Code, send the x-session-affinity or x-opencode-session header with your requests; OmniRoute will automatically pin subsequent calls to the same Claude connection using its SQLite-based sessionAccountAffinity store with configurable TTL.

Session affinity (also known as sticky sessions) ensures that all requests from a single logical session route to the same provider connection. For Claude Code—the OpenCode-compatible executor in OmniRoute—this prevents connection churn and maintains stateful consistency when interacting with Anthropic's Claude models. This guide explains how session affinity works in OmniRoute based on the source code implementation.

Understanding Session Affinity in OmniRoute

OmniRoute implements session affinity through a three-layer architecture: header detection, database pinning, and connection selection. The system transparently handles Claude Code requests the same way it processes other OpenCode-compatible executors.

Header Detection and Normalization

OmniRoute accepts two header variants for session affinity:

  • x-session-affinity – Generic header recognized across all executors
  • x-opencode-session – Provider-specific header for OpenCode/Claude Code

When OmniRoute receives a request with x-session-affinity, it automatically maps this value to x-opencode-session for the OpenCode executor. This normalization ensures consistent behavior regardless of which header the client sends. The Opencode executor test suite verifies this fallback behavior at [tests/unit/opencode-executor.test.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/tests/unit/opencode-executor.test.ts).

Database Pinning with TTL

Upon detecting an affinity header, OmniRoute stores a pin in the sessionAccountAffinity SQLite table via the upsertSessionAccountAffinity function in [src/lib/db/sessionAccountAffinity.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/db/sessionAccountAffinity.ts). Each pin contains:

Field Purpose
sessionId The affinity header value (e.g., my-session-123)
provider Provider name (anthropic for Claude)
connectionId Specific connection instance to pin
createdAt Timestamp for TTL calculation
ttlMs Time-to-live in milliseconds (default: 60,000ms)

The TTL mechanism ensures that stale pins automatically expire, preventing indefinite routing to potentially degraded connections.

Connection Selection Logic

The core affinity resolution logic resides in [src/sse/services/sessionAffinityPin.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/sse/services/sessionAffinityPin.ts). During credential resolution (getProviderCredentials), OmniRoute:

  1. Checks for an active affinity pin matching the session ID and provider
  2. Validates that the pinned connection is healthy (no circuit-breaker open, no cooldown state)
  3. Returns the pinned connection if valid; otherwise falls back to standard provider selection

This flow guarantees that Claude Code requests with matching session headers consistently hit the same Claude instance until the pin expires or the connection becomes unhealthy.

Step-by-Step Configuration

Step 1: Enable Session Affinity Headers in Client Requests

Add either header to your Claude Code API calls. The x-session-affinity header works universally:

// Client-side: Claude Code request with session affinity
const response = await fetch('http://localhost:20128/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-session-affinity': 'claude-session-abc123',
  },
  body: JSON.stringify({
    model: 'anthropic/claude-3.5-sonnet',
    messages: [
      { role: 'user', content: 'Explain session affinity benefits.' },
    ],
  }),
});

For OpenCode-native clients, you may alternatively use x-opencode-session; OmniRoute treats both identically after normalization.

Step 2: Verify Provider Configuration

Ensure your OmniRoute deployment has an active Anthropic provider configured. Session affinity only functions when the provider resolver can match the session to an available connection. The affinity pin is provider-scoped—requests for anthropic/claude-3.5-sonnet will not share pins with other providers.

Step 3: Adjust TTL if Needed (Optional)

The default 60-second TTL suits most interactive Claude Code sessions. For longer-running conversations, you may need to extend this value. The TTL is specified in milliseconds during the upsertSessionAccountAffinity call:

// Extended TTL for long-running Claude sessions (5 minutes)
await upsertSessionAccountAffinity(
  'claude-session-abc123',
  'anthropic',
  connection.id,
  Date.now(),
  300000  // 5 minutes in milliseconds
);

Refer to your deployment's environment configuration or custom middleware to override default TTL values.

Handling Connection Failures and Eviction

Session affinity gracefully degrades when pinned connections become unavailable. OmniRoute's test suite validates two critical failure scenarios:

Sticky Affinity Failover

The test at [tests/unit/sticky-affinity-failover-6219.test.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/tests/unit/sticky-affinity-failover-6219.test.ts) confirms that a failed connection does not immediately erase the affinity pin. Instead, OmniRoute marks the connection unavailable and falls back to standard selection for the current request—preserving the pin in case the connection recovers.

TTL-Based Eviction

The combo timeout test at [tests/unit/session-affinity-combo-timeout-eviction.test.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/tests/unit/session-affinity-combo-timeout-eviction.test.ts) demonstrates automatic pin removal after TTL expiration, ensuring stale session data does not accumulate.

Manual Eviction

For explicit cleanup—such as when a connection is permanently decommissioned—use the eviction helper:

import { evictSessionAccountAffinityForConnection } from '@/src/lib/db/sessionAccountAffinity';

// Remove specific session-provider-connection binding
await evictSessionAccountAffinityForConnection(
  'claude-session-abc123',
  'anthropic',
  decommissionedConnectionId
);

Server-Side Implementation Reference

The following simplified example shows how OmniRoute internally handles affinity-aware Claude requests:

// Simplified server-side flow based on OmniRoute source
import { upsertSessionAccountAffinity } from '@/src/lib/db/sessionAccountAffinity';
import { getProviderCredentials } from '@/open-sse/services/auth';

export async function handleClaudeRequest(request: Request) {
  // 1. Extract and normalize affinity header
  const affinityHeader = 
    request.headers.get('x-session-affinity') ??
    request.headers.get('x-opencode-session');

  if (affinityHeader) {
    // 2. Store affinity pin with default TTL
    await upsertSessionAccountAffinity(
      affinityHeader,
      'anthropic',
      selectedConnection.id,
      Date.now(),
      60000  // Default 60s TTL
    );
  }

  // 3. Credential lookup respects active pins automatically
  const credentials = await getProviderCredentials('anthropic', {
    session: affinityHeader,
  });

  // 4. Execute request through pinned or newly selected connection
  return executeClaudeRequest(credentials, request);
}

This pattern matches the actual implementation in [src/sse/services/sessionAffinityPin.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/sse/services/sessionAffinityPin.ts), which orchestrates header parsing, database operations, and connection resolution.

Key Source Files

Component Path Responsibility
Affinity pinning service [src/sse/services/sessionAffinityPin.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/sse/services/sessionAffinityPin.ts) Header parsing, pin lookup, connection selection
Database operations [src/lib/db/sessionAccountAffinity.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/db/sessionAccountAffinity.ts) upsertSessionAccountAffinity, getSessionAccountAffinity, evictSessionAccountAffinityForConnection
Header mapping tests [tests/unit/opencode-executor.test.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/tests/unit/opencode-executor.test.ts) Validates x-session-affinityx-opencode-session fallback
Failover behavior [tests/unit/sticky-affinity-failover-6219.test.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/tests/unit/sticky-affinity-failover-6219.test.ts) Connection failure handling without pin destruction
TTL eviction [tests/unit/session-affinity-combo-timeout-eviction.test.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/tests/unit/session-affinity-combo-timeout-eviction.test.ts) Automatic expiration of stale affinity pins

Summary

  • Send x-session-affinity (or x-opencode-session) with Claude Code requests to enable sticky sessions
  • OmniRoute normalizes headers automatically and stores pins in sessionAccountAffinity with configurable TTL
  • Connection selection in sessionAffinityPin.ts prefers pinned connections, falling back gracefully when unavailable
  • TTL and eviction prevent stale routing; failed connections trigger failover without immediate pin deletion
  • Manual eviction via evictSessionAccountAffinityForConnection handles permanent connection decommissioning

Frequently Asked Questions

What happens if I don't send a session affinity header?

Without x-session-affinity or x-opencode-session, OmniRoute routes each Claude Code request independently through standard provider selection. You lose connection stickiness but gain maximum load distribution across available Claude instances.

Can I use session affinity across different providers?

No. Session affinity pins are scoped to a specific provider (e.g., anthropic). A session ID used with Claude Code will not influence routing for requests directed at OpenAI, Google, or other providers—even if the session ID string is identical.

How do I debug why session affinity isn't working?

Verify three things: (1) the header is present and non-empty in your request, (2) the target provider has healthy connections available when the first request arrives, and (3) the TTL hasn't expired between requests. Check OmniRoute logs for upsertSessionAccountAffinity and getSessionAccountAffinity operations to confirm database persistence.

Does session affinity survive OmniRoute restarts?

Yes. Because OmniRoute persists affinity pins in SQLite via sessionAccountAffinity.ts, pins survive process restarts as long as the database file is preserved. TTL clocks resume from stored timestamps, so expired pins will be cleaned up normally after restart.

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 →