fix(updater): write models to settings.availableModels — v2.1.114+ removed CLAUDE_CUSTOM_MODELS env var
Root cause of "/model picker shows only 5 models": Anthropic removed
the CLAUDE_CUSTOM_MODELS env var sometime around v2.1.114. The picker
now reads exclusively from settings.json `availableModels` (a Zod-typed
allowlist field). We were:
- Writing CLAUDE_CUSTOM_MODELS into env.* (no longer read)
- Calling data.pop("availableModels", None) — actively REMOVING the
field that the picker now needs
So the picker fell back to built-in models + the two ANTHROPIC_DEFAULT_*
models, giving exactly 4 entries. Confirmed via strings on the v2.1.119
SEA binary: zero hits for "CLAUDE_CUSTOM_MODELS", but Zod schema
defines availableModels as the model allowlist.
Fix:
- patch_user() writes config['models'] into data['availableModels']
(still also sets the env var for backward-compat with side-by-side
older binaries)
- Verification block in install.sh + updater.py now reports both:
availableModels: N models (env.CLAUDE_CUSTOM_MODELS legacy: N)
Tested locally on v2.1.119 SEA install → settings.json now contains
availableModels with all 19 models. Restart claude → /model should show
the full list.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -179,13 +179,20 @@ try:
|
|||||||
s = json.load(open(sys.argv[1]))
|
s = json.load(open(sys.argv[1]))
|
||||||
env = s.get("env", {})
|
env = s.get("env", {})
|
||||||
ccm = env.get("CLAUDE_CUSTOM_MODELS", "")
|
ccm = env.get("CLAUDE_CUSTOM_MODELS", "")
|
||||||
n = len(ccm.split(",")) if ccm else 0
|
n_env = len(ccm.split(",")) if ccm else 0
|
||||||
|
am = s.get("availableModels") or []
|
||||||
|
n_am = len(am) if isinstance(am, list) else 0
|
||||||
print(f" base_url: {env.get('ANTHROPIC_BASE_URL','-')}")
|
print(f" base_url: {env.get('ANTHROPIC_BASE_URL','-')}")
|
||||||
auth = env.get("ANTHROPIC_AUTH_TOKEN", "")
|
auth = env.get("ANTHROPIC_AUTH_TOKEN", "")
|
||||||
print(f" auth_token: {auth[:8]}{'...' if auth else ' (NOT SET — auth will fail!)'}")
|
print(f" auth_token: {auth[:8]}{'...' if auth else ' (NOT SET — auth will fail!)'}")
|
||||||
print(f" custom_models: {n} models {'⚠ ZERO — /model will show built-ins only' if n==0 else 'OK'}")
|
# SEA v2.1.114+ reads from settings.availableModels (settings.json),
|
||||||
if n > 0:
|
# not env.CLAUDE_CUSTOM_MODELS. We log both: availableModels is the
|
||||||
print(f" sample: {','.join(ccm.split(',')[:3])}...")
|
# source of truth for the /model picker now.
|
||||||
|
status = "OK" if n_am > 0 else "WARN ZERO — /model will show built-ins only"
|
||||||
|
print(f" availableModels: {n_am} models {status}")
|
||||||
|
print(f" env.CLAUDE_CUSTOM_MODELS (legacy): {n_env} models")
|
||||||
|
if n_am > 0:
|
||||||
|
print(f" sample: {','.join(am[:3])}...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ERROR reading settings.json: {e}")
|
print(f" ERROR reading settings.json: {e}")
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|||||||
@@ -855,6 +855,10 @@ def patch_user(user_home, user_name, uid, gid, config):
|
|||||||
env.pop("ANTHROPIC_MODEL", None)
|
env.pop("ANTHROPIC_MODEL", None)
|
||||||
if "timeout_ms" in config and config["timeout_ms"] is not None:
|
if "timeout_ms" in config and config["timeout_ms"] is not None:
|
||||||
env["API_TIMEOUT_MS"] = str(config["timeout_ms"])
|
env["API_TIMEOUT_MS"] = str(config["timeout_ms"])
|
||||||
|
# NOTE: CLAUDE_CUSTOM_MODELS env var was removed upstream in v2.1.114+.
|
||||||
|
# The /model picker now reads from settings.json `availableModels` array
|
||||||
|
# (see Zod schema in cli.js / SEA binary). We still set the env var for
|
||||||
|
# backward-compat with any older binary that may be hanging around.
|
||||||
if config.get("models"):
|
if config.get("models"):
|
||||||
env["CLAUDE_CUSTOM_MODELS"] = ",".join(config["models"])
|
env["CLAUDE_CUSTOM_MODELS"] = ",".join(config["models"])
|
||||||
if config.get("default_sonnet_model"):
|
if config.get("default_sonnet_model"):
|
||||||
@@ -873,7 +877,14 @@ def patch_user(user_home, user_name, uid, gid, config):
|
|||||||
data["env"] = env
|
data["env"] = env
|
||||||
data["model"] = config["model"]
|
data["model"] = config["model"]
|
||||||
data.pop("models", None)
|
data.pop("models", None)
|
||||||
data.pop("availableModels", None)
|
# SEA v2.1.114+: settings.json `availableModels` is the allowlist that
|
||||||
|
# populates the /model picker. Empty array = only default visible.
|
||||||
|
# Undefined = built-ins only. We write the full models list so the picker
|
||||||
|
# shows everything from patcher.config.json.
|
||||||
|
if config.get("models"):
|
||||||
|
data["availableModels"] = list(config["models"])
|
||||||
|
else:
|
||||||
|
data.pop("availableModels", None)
|
||||||
data["effortLevel"] = config.get("effort_level", "high")
|
data["effortLevel"] = config.get("effort_level", "high")
|
||||||
|
|
||||||
theme = config.get("theme")
|
theme = config.get("theme")
|
||||||
@@ -1011,8 +1022,11 @@ def patch_all_users(config):
|
|||||||
env_block = written.get("env", {})
|
env_block = written.get("env", {})
|
||||||
ccm = env_block.get("CLAUDE_CUSTOM_MODELS", "")
|
ccm = env_block.get("CLAUDE_CUSTOM_MODELS", "")
|
||||||
ccm_count = len(ccm.split(",")) if ccm else 0
|
ccm_count = len(ccm.split(",")) if ccm else 0
|
||||||
print(f" {G}Patched {user.name}{D}: {path} "
|
am = written.get("availableModels") or []
|
||||||
f"(env.CLAUDE_CUSTOM_MODELS={ccm_count} models)")
|
am_count = len(am) if isinstance(am, list) else 0
|
||||||
|
print(f" {G}Patched {user.name}{D}: {path}")
|
||||||
|
print(f" availableModels: {am_count} models "
|
||||||
|
f"(env.CLAUDE_CUSTOM_MODELS legacy: {ccm_count})")
|
||||||
except Exception:
|
except Exception:
|
||||||
print(f" {G}Patched {user.name}{D}: {path}")
|
print(f" {G}Patched {user.name}{D}: {path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user