agent: Claude Code is told the window of Claude Desktop's models while Desktop is on magpie, so its Code tab no longer shows GPT 272K and Grok 256K as 200K (#1458)

Claude Desktop's Code tab runs Claude Code on ~/.claude/settings.json
and hands it the id Desktop lists a model by (mythos-magpie-<n>,
anthropic/magpie-<n>), with nothing of the model's window. Claude Code
2.1.293 (the one Desktop 2.31226.1 ships) gives an id it doesn't know
200000 unless the id has [1m] or CLAUDE_CODE_MAX_CONTEXT_TOKENS is set:
its catalog rows come from Anthropic's served catalog alone, and gateway
discovery keeps only id, name and description. Desktop sets that env
only for Cowork (yukon-silver-config contextWindowByModel), never for a
Code session, and reads the gateway's max_input_tokens only to add a
1M entry. magpie wrote the env from Claude Code's own main model alone,
and not at all on a [1m] or Claude main model, so GPT-6-Astra and Grok
4.7 ran as 200K. GLM 5.3 Flash is 1M, listed as [1m], so it ran at 1M
and showed magpie's auto-compact window, 300K.

While Claude Desktop is on magpie, the window written is now the least
of Claude Code's own model's and of the models Desktop is shown, leaving
out one listed by its 1M id (1M whatever the env says) and one named as a
Claude model Claude Code knows. Claude Code takes one value for every
model it doesn't know, so it is the least: never more than any of them
takes (an overflow is still turned into "prompt is too long" by the
gateway). A value the user set is still left alone. Sync writes it too,
so a model added or hidden for Desktop moves it, and turning
DesktopLongest on or off tells the agents again (catalog.Touched).
gateway.DesktopListed1M is the one rule desktopModels lists [1m] by, so
bb3b3f14's 1M label is unchanged.

Standalone Claude Code has the same limit: a magpie model picked in
/model runs on the one value; that is not changed beyond the Desktop
case.

Run:
- TestClaudeDesktopContextWindow, TestClaudeDesktopContextWindow1M fail
  on the old claude.go/desktop.go ("" want 256000; "" want 1000000), and
  each exclusion broken by hand turns them red.
- Real Claude Code 2.1.293 (from Desktop 2.31226.1's download), sandbox
  HOME/CLAUDE_CONFIG_DIR, against a fake gateway on 3611, with the
  settings.json magpie's agent code wrote (GPT 272K, Grok 256K, GLM 1M
  under DesktopLongest, main on GLM [1m]): /context shows 256k for
  GPT's and Grok's mythos-magpie ids (200k before), 272k for GLM's
  [1m] id (the compact window).
- gofmt -l; go vet, GOOS=linux and GOOS=windows go vet -tags nogui on
  internal/agent, internal/gateway, internal/gui; go test -tags nogui
  ./... under a temp HOME.
Not run: the Claude Desktop GUI itself.
This commit is contained in:
yetone committed 2026-10-10 11:42:03 +08:00
1 parent d7a1b02032
commit 43cb6fe34e
4 files changed
+262 -14

No files matched your search

+69 -13
View File
@@ -166,7 +166,9 @@ var claudeAliases = []string{"default", "best", "opus", "sonnet", "haiku", "fabl
// auto-compact window (CLAUDE_CODE_AUTO_COMPACT_WINDOW, the autoCompactWindow
// setting) is only ever the smaller of its own value and this one, so this
// is what tells it where a 128K or a 400K model runs out. A name marked [1m]
// is 1M whatever it says.
// is 1M whatever it says. It is the one window Claude Code knows for any
// such model (2.1.293: none is taken from a gateway), Claude Desktop's Code
// tab's too, which runs on this settings.json (claudeDesktopWindow).
const claudeContextEnv = "CLAUDE_CODE_MAX_CONTEXT_TOKENS"
// claudeCompactEnv is where Claude Code compacts a conversation, the smaller
@@ -650,13 +652,15 @@ func claudeIn(at place) *Agent {
// writeCaps says what magpie's models can do, the ones in models first;
// Claude Desktop's ids too while it runs on magpie, its Code tab being
// Claude Code on this settings.json
desktopOn := func() bool {
return at.id == "" && at.sys == nil && desktopWired(desktopPathsOf(desktopDirs(desktopdir.OS, at.home, os.Getenv)))
}
writeCaps := func(models ...string) error {
if env(claudeCapsEnv) != "" && !capsOurs() {
forget(capsKey)
return nil
}
desktop := at.id == "" && at.sys == nil && desktopWired(desktopPathsOf(desktopDirs(desktopdir.OS, at.home, os.Getenv)))
v := claudeCapabilities(models, desktop)
v := claudeCapabilities(models, desktopOn())
if v == "" {
return dropCaps()
}
@@ -666,6 +670,31 @@ func claudeIn(at place) *Agent {
stash(map[string]string{capsKey: v})
return edit.SetJSON(path, edit.KV{Path: "env." + claudeCapsEnv, Value: v})
}
// writeWindow tells Claude Code the window of the models it runs on
// (claudeWindow), and while Claude Desktop runs on magpie, of those its
// Code tab runs it on too (claudeDesktopWindow): the least of them, as
// one value serves them all. One the user set is theirs.
writeWindow := func(main string, tiers map[string]string) error {
if env(claudeContextEnv) != "" && !windowOurs() {
forget(windowKey)
return nil
}
w := claudeWindow(main, tiers)
if desktopOn() {
if d := claudeDesktopWindow(); d > 0 && (w == 0 || d < w) {
w = d
}
}
if w <= 0 {
return dropWindow()
}
v := strconv.Itoa(w)
stash(map[string]string{windowKey: v})
if v == env(claudeContextEnv) {
return nil
}
return edit.SetJSON(path, edit.KV{Path: "env." + claudeContextEnv, Value: v})
}
// what was last written that an open Claude Code session doesn't see:
// it reads settings.json at start-up, only its env as it goes
stale := ""
@@ -908,15 +937,8 @@ func claudeIn(at place) *Agent {
stash(map[string]string{mainKey: main, wroteAt: at.gw()})
// the new model's window replaces the old one's, when magpie knows
// it; one the user set is theirs
if env(claudeContextEnv) == "" || windowOurs() {
if w := claudeWindow(main, tiers); w > 0 {
kvs = append(kvs, edit.KV{Path: "env." + claudeContextEnv, Value: strconv.Itoa(w)})
stash(map[string]string{windowKey: strconv.Itoa(w)})
} else if err := dropWindow(); err != nil {
return err
}
} else {
forget(windowKey)
if err := writeWindow(main, tiers); err != nil {
return err
}
models := []string{main}
for _, t := range claudeTiers {
@@ -1312,8 +1334,15 @@ func claudeIn(at place) *Agent {
}
}
models := []string{mainModel()}
tiers := map[string]string{}
for _, t := range claudeTiers {
models = append(models, env(tierEnv(t)))
tiers[t] = env(tierEnv(t))
models = append(models, tiers[t])
}
// the windows of the models as they are now, Claude Desktop's
// among them (#1458)
if err := writeWindow(mainModel(), tiers); err != nil {
return err
}
if err := writeCompact(); err != nil {
return err
@@ -1682,6 +1711,33 @@ func claudeWindow(main string, tiers map[string]string) int {
return w
}
// claudeDesktopWindow is the least window of the models Claude Desktop's
// Code tab runs Claude Code on that it takes claudeContextEnv for, 0 when
// it knows none. Desktop hands Claude Code the id it lists a model by
// (gateway.DesktopID) and nothing of its window: it reads the gateway's
// max_input_tokens only to offer a 1M entry (2.31226.1), and Claude Code
// takes no window from a gateway's /v1/models (its model catalog is
// Anthropic's alone, and even there one past 200K counts as 200K for a
// model it doesn't know). So a mythos-magpie id of a 272K model ran as
// 200K, and with the auto-compact window shown in its place, a 1M one as
// that (#1458). Left out are a model listed by its "[1m]" id, which is 1M
// whatever this says, and one named as a Claude model Claude Code knows,
// which has its own; Claude Code takes one value for all the rest, so it
// is the least, which none of them outgrows.
func claudeDesktopWindow() int {
shown, _ := provider.CatalogFor("claude-desktop")
w := 0
for _, e := range shown {
if e.Context <= 0 || gateway.DesktopListed1M(e) || claudeName(gateway.DesktopID(e)) != "" {
continue
}
if w == 0 || e.Context < w {
w = e.Context
}
}
return w
}
// claudeOutput is the reply length to tell Claude Code for the models it
// runs on (main first), 0 to leave it to Claude Code: when the main model is
// a Claude one, which Claude Code knows; when a model it runs on that isn't
@@ -0,0 +1,176 @@
package agent
import (
"path/filepath"
"strings"
"testing"
"github.com/yetone/magpie/internal/catalog"
"github.com/yetone/magpie/internal/edit"
"github.com/yetone/magpie/internal/gateway"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/settings"
)
// desktopWindowSandbox is a HOME with provider v's models: gpt 272K and
// grok 256K (reasoning, so Desktop lists them by mythos-magpie ids), glm
// 1M, plain of no known window, and a Claude-named one Claude Code knows.
func desktopWindowSandbox(t *testing.T) (home, path string) {
home = t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("APPDATA", filepath.Join(home, "AppData", "Roaming"))
t.Setenv("LOCALAPPDATA", filepath.Join(home, "AppData", "Local"))
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
if err := provider.Save(provider.Provider{ID: "v", Name: "V", Chat: "http://127.0.0.1:1/v1", Key: "k",
Models: []string{"gpt", "grok", "glm", "plain", "claude-opus-4-6"}}); err != nil {
t.Fatal(err)
}
if err := catalog.SaveLive("v", "http://127.0.0.1:1/v1", []catalog.Model{
{ID: "gpt", Context: 272000}, {ID: "grok", Context: 256000}, {ID: "glm", Context: 1000000},
{ID: "plain"}, {ID: "claude-opus-4-6", Context: 128000},
}); err != nil {
t.Fatal(err)
}
for _, id := range []string{"v/gpt", "v/grok", "v/claude-opus-4-6"} {
if err := provider.SetModelEfforts(id, []string{"low", "medium", "high"}); err != nil {
t.Fatal(err)
}
}
path = filepath.Join(home, ".claude", "settings.json")
writeFile(t, path, `{"theme":"dark"}`)
return home, path
}
// TestClaudeDesktopContextWindow (#1458): Claude Desktop's Code tab runs
// Claude Code on ~/.claude/settings.json with the id Desktop lists a model
// by, and hands it nothing of the model's window; Claude Code 2.1.293 takes
// a mythos-magpie id it doesn't know for 200K unless
// CLAUDE_CODE_MAX_CONTEXT_TOKENS says otherwise, so GPT 272K and Grok 256K
// showed "/ 200K". While Desktop is on magpie, magpie tells Claude Code the
// least window of Desktop's models (it takes one value for all of them),
// whatever model Claude Code itself is on, and never more than one of them
// takes; a model Claude Code knows, and one listed by its 1M id, are left
// out; the user's own value is theirs.
func TestClaudeDesktopContextWindow(t *testing.T) {
home, path := desktopWindowSandbox(t)
a := claude(home)
window := func() string { v, _ := edit.GetJSON(path, "env."+claudeContextEnv); return v }
set := func(key, v string) {
t.Helper()
if err := a.Field(key).Set(v); err != nil {
t.Fatal(err)
}
}
desktop := func(v string) {
t.Helper()
if err := claudeDesktop(home).Field("provider").Set(v); err != nil {
t.Fatal(err)
}
}
// the ids Desktop's Code tab hands Claude Code are ones it doesn't know
for _, e := range provider.Catalog() {
if e.ID == "v/gpt" || e.ID == "v/grok" {
if id := gateway.DesktopID(e); !strings.HasPrefix(id, "mythos-magpie-") || claudeName(id) != "" {
t.Fatalf("%s is listed as %s", e.ID, id)
}
}
}
// Claude Code on magpie on a model of no known window: nothing to
// tell it until Desktop is on magpie
set("model", "v/plain")
if window() != "" {
t.Fatalf("plain, Desktop off: %q", window())
}
desktop("magpie")
if window() != "256000" {
t.Fatalf("Desktop on magpie, Claude Code on plain: %q, want Grok's 256000", window())
}
// Claude Code's own model of 272K is told no more than Desktop's 256K
set("model", "v/gpt")
if window() != "256000" {
t.Fatalf("main gpt beside Desktop's grok: %q", window())
}
// a [1m] main model, which before told nothing at all
set("model", "v/glm[1m]")
if window() != "256000" {
t.Fatalf("[1m] main model: %q", window())
}
// a model added later reaches it with the rest of the catalog
if err := provider.Save(provider.Provider{ID: "w", Name: "W", Chat: "http://127.0.0.1:1/v1", Key: "k", Models: []string{"small"}}); err != nil {
t.Fatal(err)
}
if err := catalog.SaveLive("w", "http://127.0.0.1:1/v1", []catalog.Model{{ID: "small", Context: 128000}}); err != nil {
t.Fatal(err)
}
if err := a.Sync(); err != nil || window() != "128000" {
t.Fatalf("after w/small was added: %q %v", window(), err)
}
if err := provider.Delete("w"); err != nil {
t.Fatal(err)
}
if err := a.Sync(); err != nil || window() != "256000" {
t.Fatalf("after w/small went: %q %v", window(), err)
}
// Desktop off: Claude Code's own model's window again, or none
desktop("")
set("model", "v/gpt")
if window() != "272000" {
t.Fatalf("Desktop off, main gpt: %q", window())
}
set("model", "claude-opus-4-8")
if window() != "" {
t.Fatalf("Desktop off, Claude's own model: %q", window())
}
// the user's own value is theirs, through Desktop, a switch and a Sync
if err := edit.SetJSON(path, edit.KV{Path: "env." + claudeContextEnv, Value: "64000"}); err != nil {
t.Fatal(err)
}
desktop("magpie")
set("model", "v/gpt")
if err := a.Sync(); err != nil || window() != "64000" {
t.Fatalf("the user's value was overwritten: %q %v", window(), err)
}
set("model", "")
if window() != "64000" {
t.Fatalf("the user's value was taken away: %q", window())
}
if v, _ := edit.GetJSON(path, "theme"); v != "dark" {
t.Fatal("theme lost")
}
}
// TestClaudeDesktopContextWindow1M: a model Desktop lists by its 1M id
// (settings.DesktopLongest) runs at 1M whatever the value says, so it adds
// nothing to it; listed plain too, it runs at the value, its own 1M when it
// is the only one.
func TestClaudeDesktopContextWindow1M(t *testing.T) {
home, path := desktopWindowSandbox(t)
// Desktop is shown glm alone (the Agents page's model list)
if err := provider.SetHiddenModels("claude-desktop", []string{"v/gpt", "v/grok", "v/plain", "v/claude-opus-4-6"}); err != nil {
t.Fatal(err)
}
a := claude(home)
window := func() string { v, _ := edit.GetJSON(path, "env."+claudeContextEnv); return v }
if err := a.Field("model").Set("v/plain"); err != nil {
t.Fatal(err)
}
if err := claudeDesktop(home).Field("provider").Set("magpie"); err != nil {
t.Fatal(err)
}
if window() != "1000000" {
t.Fatalf("glm listed plain: %q", window())
}
s := settings.Load()
s.DesktopLongest = true
if err := settings.Save(s); err != nil {
t.Fatal(err)
}
if err := a.Sync(); err != nil || window() != "" {
t.Fatalf("glm listed by its 1M id alone: %q %v", window(), err)
}
}
+11 -1
View File
@@ -174,7 +174,7 @@ func desktopModels(entries []provider.Entry) []map[string]any {
m := modelObject(e)
m["display_name"] = names[i]
m["id"] = claudeLooking(e)
if longest && e.Context >= desktop1M {
if desktopListed1M(e, longest) {
m["id"] = m["id"].(string) + "[1m]"
m["description"] = desktop1MSaid
}
@@ -207,6 +207,16 @@ func desktopModels(entries []provider.Entry) []map[string]any {
// that gives a session the 1M window.
const desktop1M = 1_000_000
// desktopListed1M says e is listed to Claude Desktop by its "[1m]" id,
// longest being settings.DesktopLongest.
func desktopListed1M(e provider.Entry, longest bool) bool { return longest && e.Context >= desktop1M }
// DesktopListed1M says Claude Desktop is shown e by its "[1m]" id, which
// its Code tab hands Claude Code, so a session on it has the 1M window.
func DesktopListed1M(e provider.Entry) bool {
return desktopListed1M(e, settings.Load().DesktopLongest)
}
// desktop1MSaid is the description Claude Desktop gives the 1M entry it
// adds (WLt in its app.asar, 2.7032).
const desktop1MSaid = "1M context window"
+6
View File
@@ -1191,11 +1191,17 @@ func Handler(w Windows, gw *gateway.Server) http.Handler {
return
}
s := settings.Load()
was := s.DesktopLongest
s.DesktopLongest = in.On
if err := settings.Save(s); err != nil {
fail(rw, err)
return
}
// a model listed by its 1M id leaves the window Claude Code is
// told for Desktop's others, or joins it (#1458)
if was != in.On {
catalog.Touched()
}
writeJSON(rw, settingsState())
})
// whether Codex and Claude Code are told a model's whole context window