Skip to content

Claude models read the way Claude Code lists them: Fable 5.2, not fable - #216

Merged
webdevcody merged 1 commit into
mainfrom
claude-model-names
Nov 11, 2026
Merged

webdevcody merged 1 commit into
mainfrom
claude-model-names

Conversation

@webdevcody

Copy link
Copy Markdown
Contributor

Every place nebula printed a Claude model showed the id it stores and passes to --model — fable, which says nothing about which Fable — and now shows the name Claude Code's own /model picker lists: Fable 5.2.

Contents: 🔁 Before / After · 📸 Screenshots · 🧭 What moved · ⚠️ Risk · 🔧 Technical overview · 📝 Notes

🔁 Before / After

Before After
Session cards, list columns, the nested strip claude fable high claude Fable 5.2 high
The 1M context window claude opus[1m] claude Opus 5.5 (1M context), the wording /model prints
QUICK PROMPT (details row and title) model fable ^O model Fable 5.2 ^O
Model submenu (^O, the NEW SESSION PICKER) fable · opus · sonnet · haiku Fable 5.2 · Opus 5.5 · Sonnet 5.5 · Haiku 5.5
Settings › Agents › Model fable Fable 5.2
Presets (the list line, the PRESET EDITOR's Model row) claude · fable · high claude · Fable 5.2 · high
Metrics overlay (warm spares) claude · fable claude · Fable 5.2
A new Claude release the alias never said which version the name follows Claude Code's catalog within ten minutes, with no nebula update
Unchanged the stored id: what --model gets, what a resume passes, what config.json holds, what type-ahead matches (fable still finds the row) same
Unchanged Codex, Cursor and custom-harness model ids same — never renamed
Unchanged an id Claude Code's catalog does not name (a Bedrock ARN, a gateway path), and every id on a machine with no catalog printed as it is

📸 Screenshots

Before After
Two session cards on the grid, their runs-on lines reading "claude fable" and "claude opus" The same two cards, their runs-on lines reading "claude Fable 5.2" and "claude Opus 5.5"
The quick prompt with the Claude model submenu open, listing default, fable, opus, sonnet, haiku The same submenu listing default, Fable 5.2, Opus 5.5, Sonnet 5.5, Haiku 5.5

How these were taken, since the stock SCREENSHOT HARNESS cannot show the change: it sets NEBULA_AGENT_CMD, under which the TUI reads none of Claude Code's files.

  • After — a scratch copy of scripts/shot/shot.sh starts the TUI alone without that switch, with CLAUDE_CONFIG_DIR on a fixture directory holding a four-model catalog (the ids and names of the real one). The DAEMON still launches the stand-in agent.
  • Before — the same binary and the same keys through the stock isolation, so no catalog is read. That is the rendering origin/main has, and the fallback this PR keeps. It is not an origin/main build.

🧭 What moved

flowchart LR
  cc["Claude Code"] -- "refreshes while it runs" --> cache["cache/model-catalog/*.json"]
  subgraph tui["TUI"]
    boot["bootstrap: at start, then every 10 min on a thread"] --> names["ModelNames: alias → id → name"]
    names --> label["model_label(kind, id)"]
    label --> cards["cards · columns · nested strip"]
    label --> box["QUICK PROMPT · model submenu"]
    label --> rest["Agents tab · presets · metrics"]
  end
  cache --> boot
  row["AGENT row: model = fable"] --> label
  row -- "unchanged" --> flag["claude --model fable"]
  classDef new fill:#bbf7d0,stroke:#166534,color:#111
  classDef same fill:#e5e7eb,stroke:#374151,color:#111
  class boot,names,label new
  class row,flag,cc,cache same
Loading

⚠️ Risk

🎯 Attack surface

  • Claude Code's catalog cache, a new file source the TUI reads. Reached by whatever can write <Claude config dir>/cache/model-catalog/ — the user's own account, the same trust as settings.json, which the TUI already reads. Held by: a 4 MB per-file cap, a plain JSON parse where a malformed file is skipped, and use of two string fields only (id, name) as display text — never passed to a CLI, a shell or the DAEMON, and ratatui drops control characters from span text. Not held: the length of a name; a very long one is drawn and truncated the way a very long model id already is.
  • settings.json's env block, read for seven provider switches. Same file and same reach as above. Held by: only whether a CLAUDE_CODE_USE_* key is non-empty is used, to pick which catalog target an alias means. Not held: nothing further — the value itself goes nowhere.

Verdict: 🟢 Low risk — display only; the id that is stored, matched and passed to the CLI does not change.

Level Why
🔒 Security & production Low the TUI reads files in the user's own Claude config dir and prints two of their strings; no new request, route, exec or write, and the DAEMON is untouched
⚡ Performance Low the TUI draw gains one read-lock, one map lookup and one short string per Claude model printed; the file reads happen once at startup (8 ms in a debug build against this machine's real cache) and then every ten minutes on a background thread, never in a key handler
🧩 Fit with the codebase Low extends claude_catalogue.rs, which already reads Claude Code's settings at TUI start for the MODEL list, and is skipped under NEBULA_AGENT_CMD exactly as that is; one shared model_label rather than a rename per surface

Rollback: git revert of the merge restores the ids on every surface; nothing is stored, migrated or versioned, so there is nothing a revert leaves behind.

🔧 Technical overview

  • What actually changed. Claude Code caches the catalog its picker is drawn from: a models table (claude-fable-5-2 → Fable 5.2) and provider_alias_targets (fable → claude-fable-5-2, with per-provider overrides). ModelNames::in_catalog reads both out of either shape Claude Code writes — the served catalog and the signed fallback document — for the cc surface only; names_from takes the newest file by its fetchedAt. config::model_label(kind, id) maps a Claude id through it and returns the id itself on a miss, and the nine call sites that formatted a model now call it.
  • Files. crates/nebula-tui/src/claude_catalogue.rs — the reader, the process-global names, the refresh thread, and its tests; crates/nebula-tui/src/config.rs — model_label / model_label_in and the Agents tab value; ui/launcher_view.rs, ui/launcher_view/nested.rs, ui/overlay.rs, quick_prompt.rs, event_loop/actions.rs, agent_presets.rs, preset_overlays.rs — one call each; docs/how-it-works.md, docs/configuration.md.
  • Why not a built-in name table. fable → Fable 5.2 compiled into nebula is wrong the day 5.3 ships, and wrong today on Foundry or a gateway, where the catalog points the same alias at an older model. With no catalog on disk the alias is printed, which is true.
  • Why not the name in the transcript. The session's model attachment carries a marketing name, but only for a session that has taken a prompt; the pickers and the Agents tab need a name before any session exists.
  • Kept as-is on purpose. A session that started on an older model in the same family is stored as the alias (existing MODEL SYNC behaviour), so its card names what the alias means now — which is also what a resume would launch. The PRESET EDITOR's type-ahead and every picker still match on the id.
  • Tests. Five new: the two catalog shapes, alias and [1m] mapping, provider overrides, newest-file selection with broken and foreign files beside it, and one wiring test that draws a card, the QUICK PROMPT row and a preset line with a name installed. Existing tests are untouched because nothing installs names in them.
  • Gate. cargo fmt --all -- --check, cargo clippy --workspace --all-targets and cargo test -p nebula-tui (1379 passed, 7 ignored) were green on the tree this was written on. That tree was a local main one commit off origin/main; the branch here is the same patch placed on origin/main (a clean three-way, identical patch-id) and was not re-run locally — CI on this PR is the gate for it. The daemon and e2e suites were not run; no daemon or protocol code changed.

📝 Notes

  • One commit on origin/main, no conflicts.
  • No upgrade step. A TUI started before the merge keeps printing ids until it is restarted.
  • Needs a Claude Code new enough to cache a model catalog (verified against 2.1.322); older versions simply leave the ids as they are.

🤖 Generated with Claude Code

A card, the quick prompt, the model submenu, the Agents tab and a preset
all printed the id nebula stores and passes to `--model` — `fable`, which
says nothing about which Fable. They now print the name Claude Code's own
`/model` picker lists: `Fable 5.2`, `Opus 5.5 (1M context)`.

The names are not hardcoded. Claude Code caches the catalog its picker is
drawn from under `<config dir>/cache/model-catalog/`; the TUI reads the
newest file at start and again every ten minutes, off the event loop, and
maps alias -> id -> name through it, following the provider Claude Code is
pointed at. The stored id is untouched, so `--model`, resume, config.json
and type-ahead are as before. An id the catalog does not name, and every
id on a machine with no catalog, prints as it is. Other harnesses' ids are
never renamed.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@claude

claude Bot commented Nov 11, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@webdevcody
webdevcody merged commit d56195b into main Nov 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant