Skip to content

feat: add enable/disable toggle for Claude Desktop routing - #1291

Closed
Splaxi wants to merge 1 commit into
lidge-jun:devfrom
Splaxi:feat/claude-desktop-toggle
Closed

feat: add enable/disable toggle for Claude Desktop routing#1291
Splaxi wants to merge 1 commit into
lidge-jun:devfrom
Splaxi:feat/claude-desktop-toggle

Conversation

@Splaxi

@Splaxi Splaxi commented Aug 8, 2026

Copy link
Copy Markdown

Adds an on/off toggle to the Claude Desktop tab, mirroring the Claude Code connection toggle. When off, the 3P config files are removed from Claude Desktop's config library; when on, they are written back from the saved profile.

Server:

  • src/types.ts: add desktopEnabled flag to OcxClaudeCodeConfig
  • src/claude/desktop-3p.ts: new clearDesktop3pConfig() to remove the opencodex entry from Claude Desktop's _meta.json and delete the config JSON file
  • src/server/management/native-integration-routes.ts: add claude-desktop to native integration client IDs, desktopStatus() probe, and handleDesktopToggle() PUT route with single-flight guard
  • src/server/management/agent-settings-routes.ts: include enabled field in GET /api/claude-desktop and /status responses

GUI:

  • gui/src/pages/ClaudeDesktop.tsx: Switch toggle above the toolbar; when OFF, hides Save & Apply (only Save remains) and shows a disabled notice
  • gui/src/pages/integrations/native-api.ts: add claude-desktop to valid client IDs
  • gui/src/pages/integrations/integration-api.ts: extract enabled from the status payload
  • gui/src/pages/integrations/overview-clients.ts: add enabled? to ClaudeDesktopPayload

i18n: 4 new keys (toggleFailed, enabledLabel, toggleAria, disabledNotice) in all 6 locales

Summary

I had big issues when trying to things, and couldn't remove the 3rd party / gateway in Claude Desktop. Had to locate the files, and simply delete them. This is to make it more user friendly.

Verification

image image

Take it as inspiration - and reshape it until it fits with what you want backed into the solution

Summary by CodeRabbit

  • New Features
    • Added Claude Desktop routing controls, including enable/disable status, toggle feedback, and accessibility labels.
    • Disabling routing now removes the integration configuration and displays a notice.
    • Save options update based on routing status, hiding “Save & Apply” when disabled.
  • Localization
    • Added Claude Desktop routing messages in English, German, Japanese, Korean, Russian, and Chinese.

Adds an on/off toggle to the Claude Desktop tab, mirroring the Claude Code connection toggle. When off, the 3P config files are removed from Claude Desktop's config library; when on, they are written back from the saved profile.

Server:
- src/types.ts: add desktopEnabled flag to OcxClaudeCodeConfig
- src/claude/desktop-3p.ts: new clearDesktop3pConfig() to remove the opencodex entry from Claude Desktop's _meta.json and delete the config JSON file
- src/server/management/native-integration-routes.ts: add claude-desktop to native integration client IDs, desktopStatus() probe, and handleDesktopToggle() PUT route with single-flight guard
- src/server/management/agent-settings-routes.ts: include enabled field in GET /api/claude-desktop and /status responses

GUI:
- gui/src/pages/ClaudeDesktop.tsx: Switch toggle above the toolbar; when OFF, hides Save & Apply (only Save remains) and shows a disabled notice
- gui/src/pages/integrations/native-api.ts: add claude-desktop to valid client IDs
- gui/src/pages/integrations/integration-api.ts: extract enabled from the status payload
- gui/src/pages/integrations/overview-clients.ts: add enabled? to ClaudeDesktopPayload

i18n: 4 new keys (toggleFailed, enabledLabel, toggleAria, disabledNotice) in all 6 locales
@github-actions github-actions Bot added the enhancement New feature or request label Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds persisted Claude Desktop routing enablement, native 3P configuration management, API status fields, GUI toggle behavior, disabled-state save handling, and localized messages.

Changes

Claude Desktop routing

Layer / File(s) Summary
Routing contracts and status
src/types.ts, src/server/management/agent-settings-routes.ts, src/server/management/native-integration-routes.ts
Adds desktopEnabled, exposes enabled in Claude Desktop responses, and detects the 3P configuration state.
Native routing toggle
src/claude/desktop-3p.ts, src/server/management/native-integration-routes.ts
Adds the Claude Desktop native client, serialized enable/disable handling, profile configuration updates, cleanup, and PUT routing.
GUI integration contracts
gui/src/pages/integrations/native-api.ts, gui/src/pages/integrations/overview-clients.ts, gui/src/pages/integrations/integration-api.ts
Adds Claude Desktop to GUI client types and maps its optional enabled status.
Desktop toggle UI and localization
gui/src/pages/ClaudeDesktop.tsx, gui/src/i18n/*.ts
Adds the toggle, request protection, status refresh, disabled notice, save behavior, and translations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClaudeDesktop as ClaudeDesktop.tsx
  participant NativeRoutes as native-integration-routes.ts
  participant Desktop3P as desktop-3p.ts
  ClaudeDesktop->>NativeRoutes: PUT /api/native-integrations/claude-desktop
  NativeRoutes->>Desktop3P: write or clear 3P configuration
  Desktop3P-->>NativeRoutes: return configuration result
  NativeRoutes-->>ClaudeDesktop: return enabled status
  ClaudeDesktop->>NativeRoutes: refresh profile and status
Loading

Possibly related PRs

Suggested reviewers: lidge-jun, wibias, ingwannu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding an enable/disable toggle for Claude Desktop routing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/claude-desktop-toggle
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
gui/src/pages/integrations/overview-clients.ts (1)

94-104: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Overview grid ignores Claude Desktop's new enabled field.

ClaudeDesktopPayload.enabled is added at Line 99, and loadClaudeDesktopStatus (gui/src/pages/integrations/integration-api.ts) already populates it end to end. But claudeDesktopRow (Lines 274-311) never reads payload.enabled. It only branches on payload.applied, payload.stale, and payload.activeProfile.

Consequence: when a user turns off "Claude Desktop routing" on the Desktop page, but a prior apply left applied: true in the backend state, the Integrations overview row still reports state: "current" / applied: true. The overview card and the "Configured clients" summary count Claude Desktop as applied even though routing is off — the file's own docstring calls this exact miscount pattern out for the Claude Code / Grok cases it fixed (isAppliedState, claudeDetailKey), but Claude Desktop was not given equivalent treatment here.

Compare with claudeRow (Lines 225-262), which derives claudeDetailKey from payload.enabled !== true and surfaces integrations.detail.claudeOff. Add an analogous branch to claudeDesktopRow, for example:

🛠️ Proposed fix
 function claudeDesktopRow(payload: ClaudeDesktopPayload | null): OverviewRow {
   const base = {
     id: "claudeDesktop" as const,
     hash: "integrations/claude/desktop",
     labelKey: "claudeDesktop.title" as TKey,
     toggle: null,
     toggleBlocked: null,
     togglePath: null,
     status: null,
     detail: null,
     detailVars: null,
   };
   if (!payload) return { ...base, state: "unknown", installed: false, applied: false, detailKey: null };
+  if (payload.enabled === false) {
+    return {
+      ...base,
+      state: "absent",
+      installed: true,
+      applied: false,
+      detailKey: "integrations.detail.claudeOff",
+    };
+  }
   if (payload.applied !== true) {

Verify with the maintainer whether the overview page is intentionally out of scope for this PR; if so, at minimum leave a comment explaining why enabled is unused here, since an unused payload field on a freshly extended type reads as an oversight.

Also applies to: 274-311

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/pages/integrations/overview-clients.ts` around lines 94 - 104, Update
claudeDesktopRow to account for payload.enabled before deriving its state from
applied, stale, or activeProfile, treating any value other than true as disabled
and surfacing the Claude Desktop equivalent of integrations.detail.claudeOff.
Preserve the existing applied/current behavior only when enabled is true, and
ensure overview applied status and configured-client counts no longer report
disabled routing as applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gui/src/pages/ClaudeDesktop.tsx`:
- Around line 317-329: Update toggleDesktop to call
toggleNativeIntegration(apiBase, "claude-desktop", next, signal) instead of
using raw fetch and readJsonOrThrow. Preserve the existing pending-state and
success handling, and catch NativeApiError so its refusal message and
reason-specific data are mapped into the existing setMessage/setAnnouncement
flow.
- Around line 317-336: Guard the optional result from readJsonOrThrow in
toggleDesktop before accessing body.message, while preserving the successful
setDesktopEnabled flow for valid responses. Only display the server message and
announcement when body exists and contains a message, so an empty or invalid
successful response cannot throw after the local state is updated.

In `@src/claude/desktop-3p.ts`:
- Around line 399-406: Validate the selected opencodex entry ID with the
project’s canonical UUID validation in a shared metadata helper before
constructing any path. Update both the deletion flow around parseMetadata and
writeDesktop3pConfig to reject non-UUID IDs, preventing join-based path
traversal while preserving the existing invalid-entry handling.

In `@src/server/management/agent-settings-routes.ts`:
- Line 665: Gate every Claude Desktop configuration writer on
config.claudeCode?.desktopEnabled: return before writeDesktop3pConfig in
autoApplyDesktopBestEffort and reject POST /api/claude-desktop/apply while
disabled. Preserve restoration only through the native toggle-enable path.
Ensure tokens and OAuth material, including the API key passed to the writer,
are never logged or serialized in responses.

In `@src/server/management/native-integration-routes.ts`:
- Around line 558-564: Update src/server/management/native-integration-routes.ts
lines 558-564 in the idempotent toggle path to verify the on-disk 3P entry and
retry writeDesktop3pConfig or clearDesktop3pConfig before returning changed:
false. Update lines 121-127 to report the observed _meta.json entry state, or
expose a separate configured-enable value, so failed cleanup is not reported as
absent.

---

Outside diff comments:
In `@gui/src/pages/integrations/overview-clients.ts`:
- Around line 94-104: Update claudeDesktopRow to account for payload.enabled
before deriving its state from applied, stale, or activeProfile, treating any
value other than true as disabled and surfacing the Claude Desktop equivalent of
integrations.detail.claudeOff. Preserve the existing applied/current behavior
only when enabled is true, and ensure overview applied status and
configured-client counts no longer report disabled routing as applied.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a4943e6a-9b67-4a27-9e53-f2562351ff81

📥 Commits

Reviewing files that changed from the base of the PR and between 57ea8df and f2a7386.

📒 Files selected for processing (14)
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/ClaudeDesktop.tsx
  • gui/src/pages/integrations/integration-api.ts
  • gui/src/pages/integrations/native-api.ts
  • gui/src/pages/integrations/overview-clients.ts
  • src/claude/desktop-3p.ts
  • src/server/management/agent-settings-routes.ts
  • src/server/management/native-integration-routes.ts
  • src/types.ts

Comment on lines +317 to +329
const toggleDesktop = async () => {
if (connectionInFlight.current) return;
connectionInFlight.current = true;
setConnectionPending(true);
setMessage(null);
const next = !desktopEnabled;
try {
const response = await fetch(`${apiBase}/api/native-integrations/claude-desktop`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: next }),
});
const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse toggleNativeIntegration instead of a raw fetch for the Desktop toggle.

gui/src/pages/integrations/native-api.ts already exports toggleNativeIntegration(apiBase, client, enabled, signal), which PUTs the same endpoint shape (/api/native-integrations/${client}), returns a typed NativeToggleEnvelope (ok, clientId, changed, state, message, reason), and throws a typed NativeApiError with a parsed .refusal (including disableBlocked reason codes like not_installed or config_busy) on failure. NativeIntegrationClientId now includes "claude-desktop" (native-api.ts Line 11), so this helper is directly usable here.

The raw fetch + readJsonOrThrow path in toggleDesktop (Lines 317-329) bypasses that refusal-aware parsing. A native refusal from the server (for example, the config file busy or Desktop not installed) will still throw via readJsonOrThrow's error path, but only as a flat string pulled from error/message, losing the reason code that the rest of the native-integration surface (claudeRow, grokRow, toggleBlocked: native?.disableBlocked) uses to drive localized, reason-specific messaging.

Do you want me to generate a version of toggleDesktop that calls toggleNativeIntegration and maps NativeApiError.refusal?.message into the existing setMessage/setAnnouncement flow?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/pages/ClaudeDesktop.tsx` around lines 317 - 329, Update toggleDesktop
to call toggleNativeIntegration(apiBase, "claude-desktop", next, signal) instead
of using raw fetch and readJsonOrThrow. Preserve the existing pending-state and
success handling, and catch NativeApiError so its refusal message and
reason-specific data are mapped into the existing setMessage/setAnnouncement
flow.

Comment on lines +317 to +336
const toggleDesktop = async () => {
if (connectionInFlight.current) return;
connectionInFlight.current = true;
setConnectionPending(true);
setMessage(null);
const next = !desktopEnabled;
try {
const response = await fetch(`${apiBase}/api/native-integrations/claude-desktop`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: next }),
});
const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed"));
setDesktopEnabled(next);
if (body.message) {
setMessage({ tone: "ok", text: body.message });
setAnnouncement(body.message);
}
void desktopResource.refresh();
void statusResource.refresh();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Unguarded body.message access on a possibly-undefined value.

readJsonOrThrow is declared as Promise<T | undefined> (see its definition in gui/src/fetch-json.ts, also surfaced in this PR's own graph context). At Line 329, body therefore has type { ok?: boolean; message?: string; state?: string } | undefined. Line 331 then reads body.message with no null check.

Every other call site of readJsonOrThrow in this same file (Lines 189, 355, 361) either discards the return value or only relies on the throw-on-!res.ok side effect. This is the only place the return value is stored and its property accessed directly, and the only place missing a guard.

Failure mode: if the PUT response is res.ok but the body doesn't parse into JSON (empty body, wrong Content-Type, truncated response), readJsonOrThrow returns undefined, and body.message throws TypeError: Cannot read properties of undefined (reading 'message') inside the try block's own success path — this crash is not caught by the surrounding catch's intended "toggle failed" messaging path in a meaningful way (it is caught, but reports a generic JS error instead of the real failure), and it also means setDesktopEnabled(next) on Line 330 has already run, leaving the toggle state locally flipped even though the "success" handling then throws.

🐛 Proposed fix
-      const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed"));
+      const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed")) ?? {};
       setDesktopEnabled(next);
       if (body.message) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleDesktop = async () => {
if (connectionInFlight.current) return;
connectionInFlight.current = true;
setConnectionPending(true);
setMessage(null);
const next = !desktopEnabled;
try {
const response = await fetch(`${apiBase}/api/native-integrations/claude-desktop`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: next }),
});
const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed"));
setDesktopEnabled(next);
if (body.message) {
setMessage({ tone: "ok", text: body.message });
setAnnouncement(body.message);
}
void desktopResource.refresh();
void statusResource.refresh();
const toggleDesktop = async () => {
if (connectionInFlight.current) return;
connectionInFlight.current = true;
setConnectionPending(true);
setMessage(null);
const next = !desktopEnabled;
try {
const response = await fetch(`${apiBase}/api/native-integrations/claude-desktop`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: next }),
});
const body = await readJsonOrThrow<{ ok?: boolean; message?: string; state?: string }>(response, t("claudeDesktop.toggleFailed")) ?? {};
setDesktopEnabled(next);
if (body.message) {
setMessage({ tone: "ok", text: body.message });
setAnnouncement(body.message);
}
void desktopResource.refresh();
void statusResource.refresh();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/pages/ClaudeDesktop.tsx` around lines 317 - 336, Guard the optional
result from readJsonOrThrow in toggleDesktop before accessing body.message,
while preserving the successful setDesktopEnabled flow for valid responses. Only
display the server message and announcement when body exists and contains a
message, so an empty or invalid successful response cannot throw after the local
state is updated.

Comment thread src/claude/desktop-3p.ts
Comment on lines +399 to +406
const entry = metadata.entries.find(e => e?.name === "opencodex" && typeof e.id === "string");
if (!entry || typeof entry.id !== "string") {
return { cleared: false, path: sentinel, reason: "no opencodex entry" };
}
const configPath = join(libraryPath, `${entry.id}.json`);
// Remove the config file — best-effort; a missing file is not a failure.
if (existsSync(configPath)) {
unlinkSync(configPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate the metadata entry ID before constructing the deletion path.

parseMetadata accepts arbitrary entry objects. Lines 399-406 only require entry.id to be a string. A crafted value such as "../../target" escapes libraryPath through join(...) and lets unlinkSync remove a reachable target.json file.

Require a canonical UUID before using an entry ID. Apply the validation in a shared metadata helper so writeDesktop3pConfig also cannot read or write through a malicious existing ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/claude/desktop-3p.ts` around lines 399 - 406, Validate the selected
opencodex entry ID with the project’s canonical UUID validation in a shared
metadata helper before constructing any path. Update both the deletion flow
around parseMetadata and writeDesktop3pConfig to reject non-UUID IDs, preventing
join-based path traversal while preserving the existing invalid-entry handling.

const state = await buildClaudeDesktopState(config);
const runtimePort = Number(url.port) || config.port;
return jsonResponse({ ...state, port: runtimePort });
return jsonResponse({ ...state, port: runtimePort, enabled: config.claudeCode?.desktopEnabled !== false });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Gate every Claude Desktop configuration writer with desktopEnabled.

The response at Line 665 reports that routing is disabled, but autoApplyDesktopBestEffort at Lines 130-150 and POST /api/claude-desktop/apply at Lines 701-754 still call writeDesktop3pConfig.

After a user disables routing, a catalog update or direct apply request can recreate the Desktop 3P entry. Both paths also pass config.apiKeys?.[0]?.key to the writer.

Return before auto-apply when config.claudeCode?.desktopEnabled === false. Reject apply requests while disabled. Allow only the native toggle enable path to restore the 3P configuration.

As per path instructions, “tokens and OAuth material must never be logged or serialized into responses.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/management/agent-settings-routes.ts` at line 665, Gate every
Claude Desktop configuration writer on config.claudeCode?.desktopEnabled: return
before writeDesktop3pConfig in autoApplyDesktopBestEffort and reject POST
/api/claude-desktop/apply while disabled. Preserve restoration only through the
native toggle-enable path. Ensure tokens and OAuth material, including the API
key passed to the writer, are never logged or serialized in responses.

Source: Path instructions

Comment on lines +558 to +564
if (desktopEnabled(config) === enabled) {
return jsonResponse({
ok: true, clientId: "claude-desktop", changed: false,
state: enabled ? "current" : "absent",
message: enabled ? "Claude Desktop routing is already on" : "Claude Desktop routing is already off",
} satisfies NativeToggleEnvelope);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reconcile failed Desktop file operations before returning an idempotent result.

The toggle persists desktopEnabled before file work. If writeDesktop3pConfig or clearDesktop3pConfig fails, the next identical PUT exits at Line 558 and never retries the incomplete operation. When disable cleanup fails, Lines 121-127 then report absent even if _meta.json still contains the opencodex entry.

  • src/server/management/native-integration-routes.ts#L558-L564: verify and reconcile the 3P entry before returning changed: false. Do not claim that a failed write “will be applied on retry” unless the same request performs that retry.
  • src/server/management/native-integration-routes.ts#L121-L127: report the observed on-disk entry state, or add a separate configured-enable field. Do not force absent when cleanup failed.
📍 Affects 1 file
  • src/server/management/native-integration-routes.ts#L558-L564 (this comment)
  • src/server/management/native-integration-routes.ts#L121-L127
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/management/native-integration-routes.ts` around lines 558 - 564,
Update src/server/management/native-integration-routes.ts lines 558-564 in the
idempotent toggle path to verify the on-disk 3P entry and retry
writeDesktop3pConfig or clearDesktop3pConfig before returning changed: false.
Update lines 121-127 to report the observed _meta.json entry state, or expose a
separate configured-enable value, so failed cleanup is not reported as absent.

@Wibias
Wibias marked this pull request as draft August 8, 2026 22:22
@lidge-jun

Copy link
Copy Markdown
Owner

Thanks for taking the time to build this, and for the note that it was meant as inspiration rather than a final shape.

This capability already shipped on dev. The Claude Desktop enable/disable toggle landed in 7f1220b4c feat(claude): add Desktop native integration toggle (PR #1106, 2026-08-06) and is released in v2.11.0 and v2.11.1, so your branch is reimplementing an existing subsystem rather than adding a missing one. I am closing this as already implemented — details below so you can find and use the existing control.

Where the feature lives today

Using it: the toggle is on the Integrations overview page, on the Claude Desktop row — not inside the Claude Desktop tab itself. Turning it off pops a confirmation dialog that names the exact config path being changed, then reverts Claude Desktop to standard mode and clears the opencodex entry. Turning it back on regenerates the config from the saved profile and the current visible model list.

Server:

  • src/types.ts / src/config.ts — the flag is clientIntegrations["claude-desktop"], on the same axis as claude, grok, and codex.
  • src/server/management/native-integration-routes.tsclaude-desktop is in NativeIntegrationClientId, desktopStatus() reports the probe, and PUT /api/native-integrations/claude-desktop performs the toggle via handleClaudeDesktopToggle().
  • Single-flight is guarded by claudeDesktopToggleFlight, which returns 409 config_busy on a concurrent write.
  • Disable calls removeDesktop3pStandardPivot() in src/claude/desktop-3p.ts, which pivots Desktop back to standard mode and reports cleanup_incomplete with residual paths, or metadata_unreadable, instead of failing silently.
  • src/server/management/agent-settings-routes.tsGET /api/claude-desktop and GET /api/claude-desktop/status already carry the desired-enabled state.

GUI:

  • gui/src/pages/integrations/overview-clients.tsclaudeDesktopRow() builds the row with toggle: "claude-desktop".
  • gui/src/pages/integrations/IntegrationsOverview.tsx — the toggle handler routes claude-desktop through toggleNativeIntegration(), with DESKTOP_DISABLE_COPY for the disable confirmation.
  • i18n keys exist under claudeDesktop.* across all six locales.

Tests: tests/native-claude-desktop-toggle.test.ts, plus the follow-up hardening in f4607d5f6, 1208f46b6, 67ebfb78e, bdd12a94f, and d9d555437.

Why this branch cannot be merged as-is

The design differs in one way that matters. This PR introduces desktopEnabled inside OcxClaudeCodeConfig and a separate clearDesktop3pConfig() that deletes the config JSON directly. dev stores the same intent in clientIntegrations["claude-desktop"] and routes removal through the standard-mode pivot. Merging both would leave two independent sources of truth for one user-visible switch, and the direct-delete path skips the residual-credential accounting the pivot performs.

On the problem that prompted this

You mentioned you could not remove the third-party gateway from Claude Desktop and had to delete the files by hand. If that happened on v2.11.0 or later, the toggle was present and something went wrong — that is a bug worth its own report. Please open a Bug report with your opencodex version, OS, and what the Claude Desktop row showed at the time, and we will look at it directly. If you were on an earlier version, upgrading gets you the control.

@lidge-jun lidge-jun closed this Aug 9, 2026
@Splaxi

Splaxi commented Aug 9, 2026

Copy link
Copy Markdown
Author

Thank you for taking the time to look into. I'll upgrade to v2.11.0 and test again 🔥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants