Migrating Model Configuration
Netclaw used to store each model role as an inline object under Models. It now stores models as named definitions and points roles at them by name, so switching a role no longer discards that model’s context window and modality overrides.
Definitions and roles arrived in netclaw 0.25.0. netclaw still reads the old inline shape and nothing rewrites it at startup, so upgrading changes nothing until you edit models. Migrate when you’re ready, or let the first model write do it for you.
Before you begin
Section titled “Before you begin”- An inline
Modelssection in~/.netclaw/config/netclaw.json. Open the file to check, becausenetclaw model listrenders both shapes identically. - No legacy
NETCLAW_Models__Main__*,__Fallback__*, or__Compaction__*variables exported anywhere the daemon can see them. They block migration outright, so convert them first. - A way to restart the daemon, if you have to convert environment variables (those need a manual restart).
The two shapes
Section titled “The two shapes”Legacy - each role carries its own copy of the model:
{ "Models": { "Main": { "Provider": "remote-gpu", "ModelId": "qwen3:30b", "ContextWindow": 32768 }, "Fallback": { "Provider": "remote-gpu", "ModelId": "qwen3:8b" } }}Canonical - models are described once, roles reference them:
{ "Models": { "Definitions": { "remote-gpu-qwen3-30b": { "Provider": "remote-gpu", "ModelId": "qwen3:30b", "ContextWindow": 32768 }, "remote-gpu-qwen3-8b": { "Provider": "remote-gpu", "ModelId": "qwen3:8b" } }, "Roles": { "Main": "remote-gpu-qwen3-30b", "Fallback": "remote-gpu-qwen3-8b" } }}A definition can also carry an optional MaxOutputTokens and Thinking level. Migration keeps a legacy role’s MaxOutputTokens, and netclaw model set --max-output-tokens and --thinking write them (see Models). Reusing a model with model set keeps the role’s own definition, so two definitions of one model, such as sonnet-low and sonnet-high, are fine.
Never both. netclaw won’t guess which one you meant, so a Models section holding inline roles and Definitions/Roles is rejected. See If you mix the two shapes.
What triggers migration
Section titled “What triggers migration”| Action | Migrates? |
|---|---|
| Upgrading netclaw | No |
| Starting the daemon | No |
netclaw model list, netclaw status | No, reads don’t write |
netclaw model set | Yes |
netclaw model clear | Yes, when the role was actually set. Clearing an unset role writes nothing |
| The model-manager TUI assigning a role | Yes |
netclaw doctor --fix | Yes |
Reading a legacy config leaves it byte-for-byte alone. The first write converts the whole Models section, not just the part you touched. We loaded a legacy file in a container, ran netclaw model list, and the file’s checksum didn’t change.
Migrating
Section titled “Migrating”Run any model write, or do it deliberately:
netclaw doctor --fix -ydoctor --fix -y printed the planned diff, wrote the file, and created the backup in our test, and it also adds configVersion. Migration is deterministic. It keeps each role’s provider, model ID, context window, modalities, and provenance, and names definitions <provider>-<model-id> with punctuation in the model ID turned into hyphens (/models/Qwen3.8-27B.gguf becomes lab--models-qwen3-8-27b-gguf). Roles that pointed at identical models collapse into one shared definition:
{ "Models": { "Definitions": { "remote-gpu-qwen3-30b": { "Provider": "remote-gpu", "ModelId": "qwen3:30b", "ContextWindow": 32768 }, "remote-gpu-qwen3-8b": { "Provider": "remote-gpu", "ModelId": "qwen3:8b", "Provenance": "Manual" } }, "Roles": { "Main": "remote-gpu-qwen3-30b", "Fallback": "remote-gpu-qwen3-8b", "Compaction": "remote-gpu-qwen3-8b" } }}Fallback and Compaction share one definition here because they were the same model. Editing it now moves both. Split them into separate definitions if you want them to drift apart.
Confirm the result:
netclaw model listThe table should read exactly as it did before. The daemon restarts itself when netclaw.json changes, so there’s nothing to restart unless legacy environment variables were involved.
Rolling back
Section titled “Rolling back”Migration first copies netclaw.json to ~/.netclaw/config/netclaw.json.legacy-models.bak - a complete snapshot of the file as it stood, not just the Models section. It’s never overwritten. If the name is taken, the next backup is netclaw.json.legacy-models.2.bak, then .3.bak, so a second migration can’t clobber your original.
An older netclaw binary can’t read the canonical shape. To downgrade, restore the backup before installing the old version:
cp ~/.netclaw/config/netclaw.json.legacy-models.bak ~/.netclaw/config/netclaw.jsonnetclaw model list # confirm the roles read as they did beforeConverting environment variables
Section titled “Converting environment variables”Legacy NETCLAW_Models__Main__* variables still work by themselves. But while any of them is set, migration stops with an error instead of proceeding, because after a restart you’d have a canonical file and legacy variables fighting over the same roles, which is the mixed shape netclaw rejects everywhere else.
Convert them first. Before:
export NETCLAW_Models__Main__Provider="openrouter"export NETCLAW_Models__Main__ModelId="anthropic/claude-sonnet-4"export NETCLAW_Models__Main__ContextWindow="200000"After:
export NETCLAW_Models__Definitions__claude__Provider="openrouter"export NETCLAW_Models__Definitions__claude__ModelId="anthropic/claude-sonnet-4"export NETCLAW_Models__Definitions__claude__ContextWindow="200000"export NETCLAW_Models__Roles__Main="claude"The definition name (claude) is yours. It has to match on both sides. Unset the old variables before migrating. Check your shell profile, your Docker env file, and any Environment= or EnvironmentFile= lines in a systemd unit.
If you mix the two shapes
Section titled “If you mix the two shapes”A Models section with Main (or Fallback, Compaction) next to Definitions and Roles is the one state netclaw refuses:
netclaw model listfails withModels configuration mixes legacy inline roles (Models:Main) with current keys (Models:Definitions, Models:Roles). Keep one set.and says which keys to remove.netclaw model setcan’t repair it.- A running daemon ignores the edit and keeps its previous models.
netclaw statusreportsconfig on disk not appliedwith the same message. netclaw doctorfails Config Schema (/Models: oneOf: Expected 1 matching subschema but found 0), Chat Client, and Context Window.- A daemon that starts on the file stops with that one-line message and no model is served. Under the Docker image the entrypoint restarts it in a loop with growing backoff.
Delete whichever representation you don’t want, keep Definitions/Roles if it’s complete, and the next start works. Pasting a bare "Main": {...} snippet from an old guide into a canonical config is the usual way to end up here.
Troubleshooting
Section titled “Troubleshooting”Legacy model roles conflict for <provider>/<model>
Section titled “Legacy model roles conflict for <provider>/<model>”Two legacy roles name the same provider and model ID but disagree on metadata, such as different ContextWindow values. Migration can’t merge them into one definition and won’t pick a winner.
Make the roles agree, then migrate:
{ "Models": { "Main": { "Provider": "remote-gpu", "ModelId": "qwen3:8b", "ContextWindow": 32768 }, "Fallback": { "Provider": "remote-gpu", "ModelId": "qwen3:8b", "ContextWindow": 32768 } }}If they genuinely need different windows, they’re different definitions. Migrate by hand and give them separate names.
Cannot migrate Models while legacy environment override ... is set
Section titled “Cannot migrate Models while legacy environment override ... is set”A NETCLAW_Models__Main__*, __Fallback__*, or __Compaction__* variable is still exported. Convert it and try again. Check the daemon’s environment too, not just your shell. Systemd units and Docker env files are the usual culprits. The error names the variable, for example Cannot migrate Models while legacy environment override 'NETCLAW_Models__Main__Provider' is set.
Models:Roles:<role> references unknown definition '<name>'
Section titled “Models:Roles:<role> references unknown definition '<name>'”A role names a definition that isn’t there, usually after a rename. Migration doesn’t cause this and can’t fix it. Compare Roles against Definitions in netclaw.json and make the names match.
Models configuration mixes legacy inline roles with named Definitions/Roles
Section titled “Models configuration mixes legacy inline roles with named Definitions/Roles”The Models section has both shapes. See If you mix the two shapes.
Models:<role> must explicitly declare Provider and ModelId before migration
Section titled “Models:<role> must explicitly declare Provider and ModelId before migration”A legacy role relied on schema defaults instead of naming its provider and model. Write them out, then migrate.
What’s next
Section titled “What’s next”Once the file is migrated, overrides live on the definition, so netclaw model set stops throwing away a model’s context window every time you reassign a role. Models covers the shape you now have.
Related pages
Section titled “Related pages”- Models - the canonical shape, field by field
netclaw model- the CLI that writes itnetclaw doctor- validation and--fix- Managed Providers - providers your definitions reference
Resources
Section titled “Resources”- .NET environment variable configuration - the
__nesting convention netclaw follows - netclaw configuration spec - the upstream reference for the
Modelssection