From 4e36a774bee81f39f78ce9feca921e59dd50f1e3 Mon Sep 17 00:00:00 2001 From: delta-cloud-208e Date: Sun, 26 Apr 2026 07:56:31 +0000 Subject: [PATCH] =?UTF-8?q?fix(updater):=20write=20models=20to=20settings.?= =?UTF-8?q?availableModels=20=E2=80=94=20v2.1.114+=20removed=20CLAUDE=5FCU?= =?UTF-8?q?STOM=5FMODELS=20env=20var?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- claude/uclaude_install.sh | 15 +++++++++++---- claude/uclaude_updater.py | 20 +++++++++++++++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/claude/uclaude_install.sh b/claude/uclaude_install.sh index 44a1dda..017ec3a 100755 --- a/claude/uclaude_install.sh +++ b/claude/uclaude_install.sh @@ -179,13 +179,20 @@ try: s = json.load(open(sys.argv[1])) env = s.get("env", {}) 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','-')}") auth = env.get("ANTHROPIC_AUTH_TOKEN", "") 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'}") - if n > 0: - print(f" sample: {','.join(ccm.split(',')[:3])}...") + # SEA v2.1.114+ reads from settings.availableModels (settings.json), + # not env.CLAUDE_CUSTOM_MODELS. We log both: availableModels is the + # 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: print(f" ERROR reading settings.json: {e}") PYEOF diff --git a/claude/uclaude_updater.py b/claude/uclaude_updater.py index b3f29d0..767d0fb 100755 --- a/claude/uclaude_updater.py +++ b/claude/uclaude_updater.py @@ -855,6 +855,10 @@ def patch_user(user_home, user_name, uid, gid, config): env.pop("ANTHROPIC_MODEL", None) if "timeout_ms" in config and config["timeout_ms"] is not None: 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"): env["CLAUDE_CUSTOM_MODELS"] = ",".join(config["models"]) if config.get("default_sonnet_model"): @@ -873,7 +877,14 @@ def patch_user(user_home, user_name, uid, gid, config): data["env"] = env data["model"] = config["model"] 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") theme = config.get("theme") @@ -1011,8 +1022,11 @@ def patch_all_users(config): env_block = written.get("env", {}) ccm = env_block.get("CLAUDE_CUSTOM_MODELS", "") ccm_count = len(ccm.split(",")) if ccm else 0 - print(f" {G}Patched {user.name}{D}: {path} " - f"(env.CLAUDE_CUSTOM_MODELS={ccm_count} models)") + am = written.get("availableModels") or [] + 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: print(f" {G}Patched {user.name}{D}: {path}") except Exception as e: