nanocoai/nanoclaw · error · Error
agent group "${groupName}" has invalid runtime_tier "${raw}"
Error message
agent group "${groupName}" has invalid runtime_tier "${raw}" — expected "container" or "vm" What it means
parseRuntimeTier rejects a stored runtime_tier value on the agent group row that is neither 'container' nor 'vm'. The column is read when materializing config (configFromDb), so a corrupt or hand-edited row aborts spawn/config load with the group name and offending value in the message.
Source
Thrown at src/container-config.ts:327
reason: err instanceof Error ? err.message : String(err),
});
}
}
return servers;
}
/**
* runtime_tier is an isolation control: dropping an unknown stored value would
* silently compose the group at the default tier — a weaker boundary than the
* one the value asked for. Fail closed instead: the group refuses to compose
* until the stored value is fixed. (A *declared* tier the driver cannot
* realize is refused separately by validateSpec, against the driver's
* capabilities.)
*/
function parseRuntimeTier(raw: string | null | undefined, groupName: string): 'container' | 'vm' | undefined {
if (raw == null) return undefined;
if (raw === 'container' || raw === 'vm') return raw;
throw new Error(`agent group "${groupName}" has invalid runtime_tier "${raw}" — expected "container" or "vm"`);
}
/**
* `'all'`, or the names the group selected. Anything else is treated as `'all'`:
* a bare string would otherwise turn an `includes` filter into a substring
* match and silently drop skills.
*
* The single reading of this column. `configFromDb` used to cast it instead,
* which threw on a corrupt row before the composer's tolerance could apply:
* every spawn failed, and `wakeContainer`'s retry contract darkened the group.
* Two readings that must agree is also how the document ends up teaching a
* skill the agent was never given.
*/
export function parseSkillSelection(raw: string | undefined, groupName: string): string[] | 'all' {
if (raw === undefined) return 'all';
let parsed: unknown;
try {
parsed = JSON.parse(raw);View on GitHub (pinned to 294ef2aee8)
Solutions
- Fix the row: UPDATE agent_groups SET runtime_tier='container' (or NULL for default) WHERE id=...
- If you meant VM isolation, set exactly 'vm'
- Avoid hand-editing; use ncl groups update so validation runs
- If this came from a migration, re-run the v2 migration and check schema_version
Example fix
-- before runtime_tier = 'docker' -- after runtime_tier = 'container'
Defensive patterns
Strategy: validation
Validate before calling
const tier = row.runtime_tier; if (tier != null && tier !== 'container' && tier !== 'vm') throw new Error(`fix runtime_tier for ${row.id}`); Type guard
function isValidRuntimeTier(v: unknown): v is 'container' | 'vm' { return v === 'container' || v === 'vm'; } Try / catch
catch (err) { if (/invalid runtime_tier/.test(err.message)) haltSpawnAndAlertOperator(err); else throw err; } Prevention
- Never hand-edit agent_groups rows; use ncl groups update
- Run migrations after upgrades so enum columns stay consistent
- Alert on spawn-time config errors — they block all sessions for that group
When it happens
Trigger: UPDATE agent_groups SET runtime_tier='docker' (or any typo/NULL-string) directly in the central DB, then spawning a container or running ncl groups config get; also a future/older schema writing an unexpected value.
Common situations: Manual SQLite edits to data/v2.db; importing rows from another install with a different schema version; a partial migration leaving stale values.
Related errors
- Agent group not found: ${agentGroupId}
- source agent group not found: ${from}
- target agent group not found: ${to}
- Container config not found for agent group: ${agentGroupId}
- agent group not found: ${agentGroupId}
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/c7c096fa4b51a4fd.
Report an issue: GitHub.