mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-10-19 07:23:36 +00:00
patch: preserve Claude Desktop model overrides (#2088)
Some checks are pending
Some checks are pending
## Why
FCC deletes Claude Desktop's saved model override during refresh,
startup, and Connect. It also reports an otherwise valid gateway profile
as disconnected whenever that override is present.
Fixes #2085.
## How
Preserve `inferenceModels`, including its order and per-model metadata,
when updating an existing FCC profile. Determine connection status from
the gateway settings without requiring the override to be absent.
Add regression coverage for refresh, Connect, credential rotation,
startup, and read-only Admin status. Fresh profiles use discovery, and
users can remove their override in Claude Desktop.
Local CI passed in a clean worktree: 9,217 Python tests and 277 browser
tests, plus formatting, lint, typing, and suppression checks.
<!-- greptile_comment -->
<!-- greptile_summary -->
<h2><a
href="https://app.greptile.com/api/retrigger?id=81273218"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=2"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"><img
alt="Retrigger"
src="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"
align="right"></picture></a><a
href="https://app.greptile.com/alishahryar1/github/Alishahryar1/free-claude-code/-/pull/2088"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptileDark.svg?v=2"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2"><img
alt="View in Greptile"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2"
align="right"></picture></a>Confidence Score: 5/5</h2>
<!-- greptile-impact-verdict --> Safe to merge based on the scoped
checks. Saved model overrides are preserved while gateway updates,
connection checks, and disconnect behavior continue to work.
<details open><summary><h3>Summary</h3></summary>
Preserves Claude Desktop's saved `inferenceModels`, including their
order and per-model metadata, when FCC updates an existing profile.
Saved overrides no longer make an otherwise valid connection appear
disconnected.
- Claude Desktop keeps saved model choices while FCC updates its gateway
profile.
</details>
<!-- greptile_confidence_score:5 -->
<sub>Reviews (1) · Last reviewed commit: ["Preserve Claude Desktop model
overrides"](c1ae11e2c9)
· [Reviewed by
Greptile](https://www.greptile.com/?utm_source=greptile_review&utm_medium=github&utm_campaign=review_public&utm_content=alishahryar1/free-claude-code)</sub>
<!-- /greptile_comment -->
This commit is contained in:
parent
261ecf7a18
commit
bd984e28d3
4 changed files with 80 additions and 4 deletions
|
|
@ -279,7 +279,6 @@ def _current(library: _Library, values: JsonObject) -> bool:
|
|||
profile.get("inferenceGatewayBaseUrl"),
|
||||
str(values["inferenceGatewayBaseUrl"]),
|
||||
)
|
||||
and "inferenceModels" not in profile
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -293,7 +292,6 @@ def _update_profile(library: _Library, values: JsonObject) -> bool:
|
|||
# Desktop otherwise restricts tool network access to the inference gateway.
|
||||
profile.setdefault("coworkEgressAllowedHosts", ["*"])
|
||||
profile["inferenceCustomHeaders"] = headers
|
||||
profile.pop("inferenceModels", None)
|
||||
return _write(library.profile_path, profile)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -80,8 +80,15 @@ def test_desktop_routes_connect_disconnect_and_hide_credentials(client):
|
|||
json.loads(profile.read_text())["inferenceGatewayBaseUrl"]
|
||||
== "http://127.0.0.1:4321"
|
||||
)
|
||||
config = json.loads(profile.read_text())
|
||||
models = [desktop_model_id("nvidia_nim/one")]
|
||||
config["inferenceModels"] = models
|
||||
profile.write_text(json.dumps(config))
|
||||
before = {path: path.stat().st_mtime_ns for path in root.rglob("*.json")}
|
||||
assert client.get(ROOT).json()["connected"] is True
|
||||
response = client.get(ROOT)
|
||||
assert response.json()["connected"] is True
|
||||
assert "desktop-secret" not in response.text
|
||||
assert json.loads(profile.read_text())["inferenceModels"] == models
|
||||
assert before == {path: path.stat().st_mtime_ns for path in root.rglob("*.json")}
|
||||
assert client.post(ROOT + "/disconnect").json()["connected"] is False
|
||||
assert not profile.exists()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from types import SimpleNamespace
|
|||
import pytest
|
||||
|
||||
from free_claude_code.config.paths import claude_desktop_disconnect_path
|
||||
from free_claude_code.core.gateway_model_ids import desktop_model_id
|
||||
from free_claude_code.harnesses import claude_desktop_integration as desktop
|
||||
from free_claude_code.harnesses.claude_desktop_integration import (
|
||||
check_unmanaged,
|
||||
|
|
@ -170,6 +171,8 @@ def test_fresh_connection_and_disconnect_preserve_native_library(tmp_path):
|
|||
assert read(meta(root))["appliedId"] == desktop.FCC_ID
|
||||
assert read(mode(root))["deploymentMode"] == "3p"
|
||||
assert read(profile(root))["inferenceGatewayApiKey"] == TOKEN
|
||||
assert read(profile(root))["modelDiscoveryEnabled"] is True
|
||||
assert "inferenceModels" not in read(profile(root))
|
||||
assert read(profile(root))["coworkEgressAllowedHosts"] == ["*"]
|
||||
before = {p: p.read_bytes() for p in root.rglob("*.json")}
|
||||
assert (
|
||||
|
|
@ -237,16 +240,71 @@ def test_connection_defaults_missing_egress_and_preserves_explicit_policy(
|
|||
assert not desktop.refresh_connected(tmp_path, URL, TOKEN, disconnect_path=record)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reconnect", [False, True])
|
||||
@pytest.mark.parametrize(
|
||||
"models",
|
||||
[
|
||||
[desktop_model_id("nvidia_nim/one"), desktop_model_id("nvidia_nim/two")],
|
||||
[
|
||||
{
|
||||
"name": desktop_model_id("nvidia_nim/two"),
|
||||
"labelOverride": "Preferred model",
|
||||
"maxEffort": "high",
|
||||
},
|
||||
{"name": desktop_model_id("nvidia_nim/one")},
|
||||
],
|
||||
],
|
||||
)
|
||||
def test_model_override_survives_refresh_and_connect(tmp_path, reconnect, models):
|
||||
record = claude_desktop_disconnect_path()
|
||||
desktop.configure(tmp_path, URL, TOKEN, True, disconnect_path=record)
|
||||
config = read(profile(tmp_path))
|
||||
config["inferenceModels"] = models
|
||||
write(profile(tmp_path), config)
|
||||
before = profile(tmp_path).read_bytes(), profile(tmp_path).stat().st_mtime_ns
|
||||
|
||||
assert desktop.configure(tmp_path, URL, TOKEN, disconnect_path=record)["connected"]
|
||||
if reconnect:
|
||||
assert desktop.configure(tmp_path, URL, TOKEN, True, disconnect_path=record)[
|
||||
"connected"
|
||||
]
|
||||
else:
|
||||
assert not desktop.refresh_connected(
|
||||
tmp_path, URL, TOKEN, disconnect_path=record
|
||||
)
|
||||
|
||||
assert read(profile(tmp_path))["inferenceModels"] == models
|
||||
assert (
|
||||
profile(tmp_path).read_bytes(),
|
||||
profile(tmp_path).stat().st_mtime_ns,
|
||||
) == before
|
||||
config.pop("inferenceModels")
|
||||
write(profile(tmp_path), config)
|
||||
assert not desktop.refresh_connected(tmp_path, URL, TOKEN, disconnect_path=record)
|
||||
assert "inferenceModels" not in read(profile(tmp_path))
|
||||
|
||||
|
||||
def test_refresh_rotates_credentials_without_reactivating(tmp_path):
|
||||
desktop.configure(
|
||||
tmp_path, URL, TOKEN, True, disconnect_path=claude_desktop_disconnect_path()
|
||||
)
|
||||
config = read(profile(tmp_path))
|
||||
models = [
|
||||
{"name": desktop_model_id("nvidia_nim/two"), "labelOverride": "Preferred"},
|
||||
{"name": desktop_model_id("nvidia_nim/one"), "maxEffort": "high"},
|
||||
]
|
||||
config["inferenceModels"] = models
|
||||
write(profile(tmp_path), config)
|
||||
assert desktop.refresh_connected(
|
||||
tmp_path,
|
||||
"http://localhost:9000",
|
||||
"new-token",
|
||||
disconnect_path=claude_desktop_disconnect_path(),
|
||||
)
|
||||
refreshed = read(profile(tmp_path))
|
||||
assert refreshed["inferenceModels"] == models
|
||||
assert refreshed["inferenceGatewayBaseUrl"] == "http://localhost:9000"
|
||||
assert refreshed["inferenceGatewayApiKey"] == "new-token"
|
||||
assert desktop.configure(
|
||||
tmp_path,
|
||||
"http://localhost:9000",
|
||||
|
|
@ -382,7 +440,7 @@ def test_other_profiles_and_unknown_settings_are_preserved(tmp_path):
|
|||
)
|
||||
assert read(profile(tmp_path))["keep"] is True
|
||||
assert read(profile(tmp_path))["inferenceCustomHeaders"]["X-Other"] == "value"
|
||||
assert "inferenceModels" not in read(profile(tmp_path))
|
||||
assert read(profile(tmp_path))["inferenceModels"] == config["inferenceModels"]
|
||||
metadata = read(meta(tmp_path))
|
||||
metadata["appliedId"] = other
|
||||
metadata["hybridPointer"] = {"bootstrapUrl": "https://example.test"}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from free_claude_code.config.api_keys import normalize_api_keys
|
|||
from free_claude_code.config.loader import ManagedConfigStore
|
||||
from free_claude_code.config.paths import claude_desktop_disconnect_path
|
||||
from free_claude_code.config.settings import Settings
|
||||
from free_claude_code.core.gateway_model_ids import desktop_model_id
|
||||
from free_claude_code.harnesses import (
|
||||
claude_desktop_integration as desktop,
|
||||
)
|
||||
|
|
@ -377,9 +378,21 @@ async def test_desktop_startup_rotates_selected_profile_and_disconnect_stays_off
|
|||
True,
|
||||
disconnect_path=claude_desktop_disconnect_path(),
|
||||
)
|
||||
profile = root / "configLibrary" / f"{desktop.FCC_ID}.json"
|
||||
config = json.loads(profile.read_text())
|
||||
models = [
|
||||
{"name": desktop_model_id("nvidia_nim/one"), "labelOverride": "Pinned model"},
|
||||
{"name": desktop_model_id("nvidia_nim/two"), "maxEffort": "high"},
|
||||
]
|
||||
config["inferenceModels"] = models
|
||||
profile.write_text(json.dumps(config))
|
||||
try:
|
||||
await runtime.start()
|
||||
await runtime._integrations._desktop_update.task
|
||||
refreshed = json.loads(profile.read_text())
|
||||
assert refreshed["inferenceModels"] == models
|
||||
assert refreshed["inferenceGatewayApiKey"] == TOKEN
|
||||
assert refreshed["inferenceGatewayBaseUrl"] == "http://127.0.0.1:8000"
|
||||
result = await runtime.claude_desktop_status()
|
||||
assert result["connected"] is True
|
||||
assert result["update"]["changed"] is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue