diff --git a/CHANGELOG.md b/CHANGELOG.md
index d63d6da93e..ae256d01cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ _In development — bullets added per PR; finalized at release._
- **fix(embeddings):** forward output dimensions to Gemini for consistent embedding dims. (thanks @nguyenha935)
- **fix(translator):** sanitize Read tool args from non-Anthropic models to prevent retry loops. (thanks @GodrezJr2)
- **fix(usage):** reuse Gemini CLI project ID for quota checks (avoid re-discovery). (thanks @Delcado19)
+- **fix(dashboard):** surface manual config CTA when Claude CLI detection fails (remote deployments). (thanks @anuragg-saxenaa)
- **fix(combo): round-robin members fail over faster under concurrency saturation via a configurable queue depth** — when a round-robin combo member was saturated, requests sat in the per-model semaphore's **unbounded** queue and only failed over to the next member after the full `queueTimeoutMs` (default 30s) elapsed — so a burst of agentic requests deep-queued one hot member instead of spilling to healthy ones. The per-model semaphore now accepts a bounded queue depth and emits `SEMAPHORE_QUEUE_FULL` once it is full (the round-robin loop already cascades on that code), so a configured low depth fails over immediately. A new `queueDepth` combo-config knob (global default / provider override / per-combo, default **20** for backward compatibility; **0** = never queue → fail over now) is exposed in Settings → Combo Defaults. ([#3872](https://github.com/diegosouzapw/OmniRoute/issues/3872) — thanks @KooshaPari)
- **fix(pricing): align Claude Code (`cc`) pricing with current Anthropic per-MTok rates** — the `cc` provider block in the default pricing table had stale numbers across every Claude 4.x family entry — most visibly, `claude-opus-4-5-20251101` was billed at the deprecated Opus 4.1 rate (`input $15` / `output $75`), and `claude-haiku-4-5-20251001` was at half the current Haiku 4.5 rate. The `cached` (cache hit) and `cache_creation` (5-minute cache write) multipliers were also off across Opus 4.6/4.7/4.8, Sonnet 4.5/4.6, Haiku 4.5, and Fable 5. All eight entries now match the rates Anthropic publishes (input, 5m cache write at 1.25x input, cache hit at 0.1x input, output; reasoning billed at the output rate), so cost accounting on the dashboard and per-request usage events stop under- or over-reporting Claude Code spend. (thanks @chulanpro5)
diff --git a/src/app/(dashboard)/dashboard/cli-code/components/ClaudeToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/ClaudeToolCard.tsx
index 8d85109303..832ff024b2 100644
--- a/src/app/(dashboard)/dashboard/cli-code/components/ClaudeToolCard.tsx
+++ b/src/app/(dashboard)/dashboard/cli-code/components/ClaudeToolCard.tsx
@@ -335,16 +335,34 @@ export default function ClaudeToolCard({
: t("installCliPrompt", { tool: "Claude" })}
-
+
+ {/*
+ Always surface Manual Config even when the CLI is not
+ detected locally — typical of remote OmniRoute
+ deployments where the CLI lives on the user's machine,
+ not on the server. Upstream report: #589.
+ */}
+
+
+
{showInstallGuide && (
diff --git a/tests/unit/ui/ClaudeToolCard-manual-config-fallback.test.tsx b/tests/unit/ui/ClaudeToolCard-manual-config-fallback.test.tsx
new file mode 100644
index 0000000000..c404218679
--- /dev/null
+++ b/tests/unit/ui/ClaudeToolCard-manual-config-fallback.test.tsx
@@ -0,0 +1,176 @@
+// @vitest-environment jsdom
+//
+// Regression test: when the Claude CLI is not detected locally (typical of
+// remote OmniRoute deployments where the CLI lives on the user's laptop, not
+// on the server), the card must still surface a "Manual Config" button so the
+// user can copy the settings.json snippet and paste it into the CLI on their
+// local machine. Before this fix the Manual Config button only rendered when
+// `cliReady === true`, which made the card useless for remote deployments
+// (upstream report: decolua/9router#589).
+import React from "react";
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+// ── Mocks ─────────────────────────────────────────────────────────────────────
+
+vi.mock("next-intl", () => ({
+ useTranslations: () => (key: string, values?: Record) => {
+ const messages: Record = {
+ cliNotInstalled: "{tool} CLI not detected locally",
+ cliNotRunnable: "{tool} CLI installed but not runnable",
+ installCliPrompt:
+ "Manual configuration is still available if OmniRoute is deployed on a remote server.",
+ cliFoundFailedHealthcheck: "{tool} CLI was found but failed runtime healthcheck{reason}.",
+ howToInstall: "How to Install",
+ hide: "Hide",
+ manualConfig: "Manual Config",
+ installationGuide: "Installation Guide",
+ platforms: "Platforms",
+ afterInstallationRun: "After installation run",
+ toVerify: "to verify.",
+ checkingCli: "Checking {tool}...",
+ claudeManualConfiguration: "Claude Manual Configuration",
+ };
+ const raw = messages[key] ?? key;
+ if (!values) return raw;
+ return Object.entries(values).reduce(
+ (acc, [k, v]) => acc.replaceAll(`{${k}}`, String(v ?? "")),
+ raw
+ );
+ },
+ useLocale: () => "en",
+}));
+
+vi.mock("@/shared/components/ProviderIcon", () => ({
+ default: () => ,
+}));
+
+vi.mock("@/app/(dashboard)/dashboard/cli-code/components/CliStatusBadge", () => ({
+ default: () => ,
+}));
+
+vi.mock("@/shared/services/claudeCliConfig", () => ({
+ getStoredClaudeAuthValue: () => null,
+ normalizeClaudeBaseUrl: (u: string) => u,
+}));
+
+// Surface ManualConfigModal as a marker so we can assert it gets rendered with
+// isOpen=true after clicking the new Manual Config button.
+vi.mock("@/shared/components", async () => {
+ const React = await import("react");
+ return {
+ Card: ({ children }: { children: React.ReactNode }) =>