{"record":{"id":"2a38f2eb3f977ae7","repo":"nanocoai/nanoclaw","slug":"stored-skill-selection-is-not-all-or-a-string-li","errorCode":null,"errorMessage":"Stored skill selection is not \"all\" or a string list; inlining every skill","messagePattern":"Stored skill selection is not \"all\" or a string list; inlining every skill","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/container-config.ts","lineNumber":351,"sourceCode":" * match and silently drop skills.\n *\n * The single reading of this column. `configFromDb` used to cast it instead,\n * which threw on a corrupt row before the composer's tolerance could apply:\n * every spawn failed, and `wakeContainer`'s retry contract darkened the group.\n * Two readings that must agree is also how the document ends up teaching a\n * skill the agent was never given.\n */\nexport function parseSkillSelection(raw: string | undefined, groupName: string): string[] | 'all' {\n  if (raw === undefined) return 'all';\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(raw);\n  } catch {\n    parsed = undefined;\n  }\n  if (parsed === 'all') return 'all';\n  if (Array.isArray(parsed) && parsed.every((n) => typeof n === 'string')) return parsed;\n  log.warn('Stored skill selection is not \"all\" or a string list; inlining every skill', { group: groupName });\n  return 'all';\n}\n\n/** Build a `ContainerConfig` from a DB row + agent group identity. */\nexport function configFromDb(row: ContainerConfigRow, group: AgentGroup): ContainerConfig {\n  return {\n    mcpServers: sanitizeStoredMcpServers(JSON.parse(row.mcp_servers), group.name),\n    packages: {\n      apt: JSON.parse(row.packages_apt) as string[],\n      npm: JSON.parse(row.packages_npm) as string[],\n    },\n    imageTag: row.image_tag ?? undefined,\n    additionalMounts: JSON.parse(row.additional_mounts) as AdditionalMountConfig[],\n    skills: parseSkillSelection(row.skills, group.name),\n    provider: row.provider ?? undefined,\n    groupName: group.name,\n    assistantName: row.assistant_name ?? group.name,\n    agentGroupId: group.id,","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/container-config.ts#L333-L369","documentation":"The `skills` column for an agent group was expected to be the JSON string `\"all\"` or a JSON array of skill-name strings, but the stored value parses to something else (or fails to parse). The parser falls back to `'all'`, inlining every skill into the container.","triggerScenarios":"`configFromDb`/`selectedSkills`/`parseSkillSelection` reading a `skills` value like `[\"a\", 1]`, `\"main\"`, `true`, or malformed JSON such as `['a','b']`.","commonSituations":"Manually editing the container_configs.skills column with non-JSON syntax; an older write path storing a bare string; a CLI flag writing unquoted values.","solutions":["Fix the stored value: `ncl groups config update --skills all` or a proper JSON array string `'[\"skill-a\"]'`","Inspect the row: `pnpm exec tsx scripts/q.ts data/v2.db \"select skills from container_configs\"`","Verify which skills get inlined after the fix"],"exampleFix":"// before (stored): skills = main\n// after (stored): skills = \"[\\\"main\\\"]\"","handlingStrategy":"validation","validationCode":"function isValidSkills(raw: string): boolean {\n  try {\n    const p = JSON.parse(raw);\n    return p === 'all' || (Array.isArray(p) && p.every((n) => typeof n === 'string'));\n  } catch { return false; }\n}","typeGuard":"function isSkillSelection(v: unknown): v is 'all' | string[] {\n  return v === 'all' || (Array.isArray(v) && v.every((n) => typeof n === 'string'));\n}","tryCatchPattern":null,"preventionTips":["Only set skills via `ncl groups config update --skills ...`","Store arrays as JSON strings: '[\"name\"]'","Never write bare comma-separated names into the column"],"tags":["skills","container-config","json-parsing"],"backgroundTag":"invalid-json-config-value","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}