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

  1. Fix the row: UPDATE agent_groups SET runtime_tier='container' (or NULL for default) WHERE id=...
  2. If you meant VM isolation, set exactly 'vm'
  3. Avoid hand-editing; use ncl groups update so validation runs
  4. 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

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


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/c7c096fa4b51a4fd. Report an issue: GitHub.