{"record":{"id":"7b7f39b5d6d97e2d","repo":"actualbudget/actual","slug":"invalid-categories-option-for-category-groups","errorCode":null,"errorMessage":"Invalid \"categories\" option for category_groups: \"${categoriesOption}\"","messagePattern":"Invalid \"categories\" option for category_groups: \"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/aql/schema/executors.ts","lineNumber":269,"sourceCode":"}\n\n// Category groups executor\n\ntype CategoriesOption = 'all' | 'none';\n\nasync function execCategoryGroups(\n  compilerState: CompilerState,\n  queryState: QueryState,\n  sqlPieces: SqlPieces,\n  params: (string | number)[],\n  outputTypes: OutputTypes,\n) {\n  const tableOptions = queryState.tableOptions || {};\n  const categoriesOption = tableOptions.categories\n    ? (tableOptions.categories as string)\n    : 'all';\n  if (!isValidCategoriesOption(categoriesOption)) {\n    throw new Error(\n      `Invalid \"categories\" option for category_groups: \"${categoriesOption}\"`,\n    );\n  }\n\n  if (categoriesOption !== 'none') {\n    return execCategoryGroupsWithCategories(\n      compilerState,\n      queryState,\n      sqlPieces,\n      params,\n      categoriesOption,\n      outputTypes,\n    );\n  }\n  return execCategoryGroupsBasic(\n    compilerState,\n    queryState,\n    sqlPieces,","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/schema/executors.ts#L251-L287","documentation":"execCategoryGroups validates the 'categories' table option for category_groups queries, which must be 'all' or 'none'. Any other value is rejected before SQL execution.","triggerScenarios":"q('category_groups', { categories: 'only' }) or any string other than 'all'/'none' (default is 'all').","commonSituations":"Copying the 'splits' option style from transactions queries, typos, or passing config-driven strings without validation.","solutions":["Use 'all' or 'none' for the categories option.","Validate the option before constructing the query.","Omit the option to get the default 'all' behavior."],"exampleFix":"// before\nq('category_groups', { categories: 'exclude-none' });\n// after\nq('category_groups', { categories: 'all' });","handlingStrategy":"validation","validationCode":"const CATEGORIES_OPTIONS = ['all','none'];\nfunction validateCategoriesOption(o) {\n  const v = o ?? 'all';\n  if (!CATEGORIES_OPTIONS.includes(v)) throw new Error(`categories must be ${CATEGORIES_OPTIONS.join(' or ')}`);\n}","typeGuard":"function isCategoriesOption(v: unknown): v is 'all'|'none' {\n  return v === 'all' || v === 'none';\n}","tryCatchPattern":"try {\n  return await q('category_groups', { categories: opt }).select('*').execute();\n} catch (e) {\n  if (e.message.includes('Invalid \"categories\" option')) {\n    return q('category_groups').select('*').execute();\n  }\n  throw e;\n}","preventionTips":["Do not reuse the transactions 'splits' values for category_groups queries","Validate any config-driven option strings at load time","Prefer omitting the option to get 'all' by default"],"tags":["aql","query","invalid-option"],"backgroundTag":"invalid-query-option","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}