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

> Learn to configure session affinity in OmniRoute for Claude Code. Pin requests to the same Claude connection using x-session-affinity headers and the SQLite store. Boost performance now.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-20

---

**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/main/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/main/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/main/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:

```typescript
// 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:

```typescript
// 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/main/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/main/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:

```typescript
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:

```typescript
// 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/main/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/main/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/main/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/main/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-affinity` → `x-opencode-session` fallback |
| Failover behavior | [[`tests/unit/sticky-affinity-failover-6219.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.