Skip to content

Claude Desktop integration cannot delete unavailable models: PUT rejects any change to them #4167

Closed

Description

@richardfeiliu-a11y

Client or integration

OpenCodex dashboard (Integrations -> Claude tab, clientId=claude-desktop), backed by GET/PUT /api/claude-desktop and POST /api/claude-desktop/apply.

Area

Dashboard

Summary

The Claude Desktop integration page cannot remove models that are no longer available. Stale claudeCode.desktopProfile.assignments accumulate forever: reconcileDesktopProfile only adds new routes and never prunes, while PUT /api/claude-desktop rejects any change touching an unavailable route. There is no supported dashboard path to delete a closed or renamed model. I expected to be able to delete unavailable assignments (or to have a prune action), while still forbidding adding or reassigning unavailable routes.

Reproduction

  1. Let claudeCode.desktopProfile.assignments accumulate stale routes (renamed providers such as GMI/*, Opencode_Go/*, opencode-zen/*, old nested CommandCode/meta/*, retired native/gpt-5.6-*). In my case: 38 assignments, 21 flagged available=false by GET /api/claude-desktop.
  2. In the dashboard Claude tab, or via API, submit a profile that removes the 21 unavailable routes and keeps only the 17 available ones (defaults unchanged, still pointing at available members).
  3. The request fails; nothing is saved.
  4. Root cause in code: PUT /api/claude-desktop in src/server/management/agent-settings-routes.ts compares current.profile.assignments[route] vs parsed.assignments[route] for every model with available=false and throws on any difference; reconcileDesktopProfile in src/claude/desktop-profile.ts only appends missing available models and preserves all stored assignments.

Workaround used (manual, disruptive): back up ~/.opencodex/config.json and the Claude-3p library profile, edit claudeCode.desktopProfile.assignments on disk to keep only the available routes, ocx restart (required because a direct file edit while the proxy runs gets overwritten by the in-memory config on the next apply), then POST /api/claude-desktop/apply, then fully quit and reopen Claude Desktop (it only reads the profile at launch). Verified: GET /api/claude-desktop went from models:38 unavailable:21 to models:17 unavailable:0; library inferenceModels 16 -> 17 (including newly appeared opencode-go/glm-5.3-flash).

Version

2.49.0

Operating system

macOS 26.6.2 (25G83)

Provider and model

Not provider-specific (affects any retired/renamed route wired to Claude Desktop; observed with CommandCode / Stepfun / google-antigravity / nvidia / opencode-go / sensenova / volcengine-agent-plan / xai catalog).

Logs or error output

{"error": "\ud604\uc7ac \uc0ac\uc6a9\ud560 \uc218 \uc5c6\ub294 \ubaa8\ub378\uc740 \uc62e\uae38 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4: CommandCode/deepseek/deepseek-v4-flash-vision-exp"}

Redacted configuration

{
  "claudeCode.desktopProfile.defaults": {
    "opus": "CommandCode/deepseek/deepseek-v4-flash",
    "fable": "native/gpt-6-astra",
    "sonnet": "CommandCode/meituan/LongCat-2.0:free",
    "haiku": "Stepfun/step-3.7-flash"
  },
  "assignments_before": 38,
  "unavailable_before": 21,
  "assignments_after": 17,
  "unavailable_after": 0
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  2. added
    bugSomething isn't working
    guiDashboard, tray, settings UI
    on Sep 10, 2026
  3. lidge-jun commented on Sep 10, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 58 / 80

    이 이슈는 OpenCodex 대시보드의 Integrations → Claude 탭(clientId=claude-desktop)에서, 카탈로그에 더 이상 없는(이름을 바꾸거나 사라진) 모델 배정을 지우지 못하는 버그를 말합니다. 지금 dev HEAD(c15a98caa) 기준으로도 그대로입니다. GET/PUT /api/claude-desktop과 POST /api/claude-desktop/apply가 이 화면의 뒷단입니다. 리포터는 배정이 38개까지 쌓였고 그중 21개가 available=false인데, 사용 가능한 17개만 남기려고 PUT을 보내면 저장이 거절됩니다. 에러 문장도 코드와 일치합니다: 「현재 사용할 수 없는 모델은 옮길 수 없습니다: …」.

    왜 이렇게 되는지는 두 군데가 같이 막기 때문입니다. 첫째, src/server/management/agent-settings-routes.ts의 PUT 핸들러는 buildClaudeDesktopState로 만든 현재 목록에서 available=false인 모든 모델에 대해, 저장 전 배정과 요청 본문 배정을 JSON.stringify로 비교합니다. 요청에서 그 라우트를 아예 빼서 지우려면 after가 undefined가 되고, 그건 “옮기기”와 똑같이 차이로 취급되어 400이 납니다. 둘째, src/claude/desktop-profile.ts의 reconcileDesktopProfile은 지금 카탈로그에 있는 모델을 배정에 추가만 하고, 예전에 남아 있는 죽은 배정을 지우지는 않습니다. 그래서 GET이 available=false로 표시해도, 정상 대시보드 저장 경로로는 그 행을 없앨 방법이 없습니다. 리포터가 말한 기대(없는 모델은 삭제·정리 허용, 없는 모델을 새로 넣거나 가족/기본값으로 옮기는 것만 금지)와 현재 가드의 동작이 어긋납니다.

    buildClaudeDesktopState(src/server/management/shared.ts)는 살아 있는 카탈로그 라우트 집합과 profile.assignments 키를 합쳐 models[].available을 만듭니다. 즉 UI는 “없다”고 보여 주지만, 저장 가드는 “없는 행의 어떤 변화도 금지”로 읽어 삭제까지 막습니다. 우회는 ~/.opencodex/config.json을 손으로 고친 뒤 ocx restart → POST /api/claude-desktop/apply → Claude Desktop 완전 종료 후 재실행인데, 프록시가 떠 있는 동안 파일만 고치면 메모리 설정이 다시 덮어씁니다. 재현·우회·파일 경로가 모두 구체적이고, types.ts/config.ts 분리 캠페인과도 겹치지 않는 독립 GUI/통합 버그입니다. 지금 dev 방향(서비스 가드·Lane B 무료 모델 체인·라운드2 문서 마감)과 직접 충돌하지는 않지만, Claude Desktop 사용자에게는 카탈로그가 바뀔 때마다 배정이 계속 불어나는 실사용 문제입니다.

    src/server/management/agent-settings-routes.ts 라인 951-956 - !item.available인 모든 라우트에 대해 before/after가 다르면 무조건 throw한다. 요청에서 키를 빼 삭제하는 경우(after === undefined)도 “옮기기”와 같이 막혀, 대시보드에서 stale assignment를 지울 수 없다.

    src/server/management/agent-settings-routes.ts 라인 958-963 - unavailable을 새 기본값으로 지정하는 것은 이미 막혀 있다. 이 부분은 유지하는 편이 맞다. 문제는 삭제까지 같은 엄격함으로 묶인 점이다.

    src/claude/desktop-profile.ts 라인 171-193 (reconcileDesktopProfile) - 카탈로그에 있는 모델만 append하고, assignments에만 남은 죽은 라우트는 절대 prune하지 않는다. PUT 가드와 합쳐지면 stale 행이 영구 잔류한다.

    src/server/management/shared.ts (buildClaudeDesktopState) - available 플래그는 UI/진단용으로 잘 드러나지만, 그 정보를 저장 정책(삭제 허용 vs 재배정 금지)으로 나누지 않는다.

    대시보드 Claude 탭 / PUT 클라이언트 - unavailable 행에 대한 삭제·정리 UX가 없다. API가 거절하면 사용자는 설정 파일을 직접 고쳐야 한다.

    메인테이너의 판단이 필요한 지점

    • 삭제를 허용할 때: PUT에서 after === undefined만 예외로 둘지, 아니면 reconcileDesktopProfile/별도 prune API로 한 번에 청소할지
    • prune 시 defaults가 unavailable을 가리키면 같은 가족 안의 available 멤버로 자동 재지정할지, 아니면 그 경우만 거절할지
    • apply(POST /api/claude-desktop/apply) 직전에도 같은 삭제 규칙을 강제할지, 저장된 프로필을 그대로 쓸지
    • 카탈로그가 잠깐 비었을 때(일시 discovery 실패) 대량 prune이 일어나면 안 되므로, “unavailable”을 신뢰하는 조건(카탈로그 healthy일 때만)을 둘지

    너의 추천
    이슈를 열고 유지한 채 help wanted로 두되, 수정 PR은 작게 가져가라. PUT 가드를 「unavailable 라우트의 family/alias 변경·추가·기본값 지정은 금지, 키 삭제(after === undefined)는 허용」으로 바꾸고, 삭제 후 defaults가 그 라우트를 가리키면 같은 family의 available 멤버로 재지정(없으면 null)하라. 이어서 reconcileDesktopProfile에 optional prune(또는 대시보드 “unavailable 정리” 액션 → PUT)을 추가해 append-only를 깨지 않으면서도 쌓인 stale을 한 번에 지울 수 있게 하라. types.ts/config.ts 분리와 무관하니 close-don't-rebase 대상이 아니고, 중복 이슈(#1213 등은 카탈로그 모드 UX)와도 합치지 마라. 회귀 테스트: unavailable 행 삭제 PUT 성공, unavailable 행 family 변경 PUT 400, available만 남긴 뒤 GET에서 unavailable=0.

    이 댓글은 grok-bot이 작성했습니다

  4. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    Owner

    Fixed on dev by #4263.
    The Claude Desktop profile PUT now distinguishes deleting an unavailable route from reassigning one, so stale assignments can be pruned from the dashboard.
    Credit to @chilung-cgu, whose commits were carried unchanged into the release chain.
    Closing manually: pull requests here target dev, so GitHub's Closes linkage only fires on a merge into main. This will reach users in the next release.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingguiDashboard, tray, settings UI

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions