{"record":{"id":"1d71bc1c8356a8cf","repo":"mastra-ai/mastra","slug":"invalid-kind-name-must-start-with-a-letter","errorCode":null,"errorMessage":"Invalid ${kind}: ${name}. Must start with a letter or underscore, contain only letters, numbers, or underscores, and be at most 63 characters long.","messagePattern":"Invalid (.+?): (.+?)\\. Must start with a letter or underscore, contain only letters, numbers, or underscores, and be at most 63 characters long\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/utils.ts","lineNumber":595,"sourceCode":"/**\n * Parses and returns a valid SQL identifier (such as a table or column name).\n * The identifier must:\n *   - Start with a letter (a-z, A-Z) or underscore (_)\n *   - Contain only letters, numbers, or underscores\n *   - Be at most 63 characters long\n *\n * @param name - The identifier string to parse.\n * @param kind - Optional label for error messages (e.g., 'table name').\n * @returns The validated identifier as a branded type.\n * @throws {Error} If the identifier does not conform to SQL naming rules.\n *\n * @example\n * const id = parseSqlIdentifier('my_table'); // Ok\n * parseSqlIdentifier('123table'); // Throws error\n */\nexport function parseSqlIdentifier(name: string, kind = 'identifier'): SqlIdentifier {\n  if (!SQL_IDENTIFIER_PATTERN.test(name) || name.length > 63) {\n    throw new Error(\n      `Invalid ${kind}: ${name}. Must start with a letter or underscore, contain only letters, numbers, or underscores, and be at most 63 characters long.`,\n    );\n  }\n  return name as SqlIdentifier;\n}\n\n/**\n * Parses and returns a valid dot-separated SQL field key (e.g., 'user.profile.name').\n * Each segment must:\n *   - Start with a letter (a-z, A-Z) or underscore (_)\n *   - Contain only letters, numbers, or underscores\n *   - Be at most 63 characters long\n *\n * @param key - The dot-separated field key string to parse.\n * @returns The validated field key as a branded type.\n * @throws {Error} If any segment of the key is invalid.\n *\n * @example","sourceCodeStart":577,"sourceCodeEnd":613,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/utils.ts#L577-L613","documentation":"`parseSqlIdentifier` validates SQL table/column/index names against `SQL_IDENTIFIER_PATTERN` (start with letter or underscore, only letters/numbers/underscores) and a 63-character limit (PostgreSQL identifier max). It throws this error to prevent SQL injection and to surface names that would be truncated or require quoting by the storage layer.","triggerScenarios":"Passing a table name, column name, conflict column, or index name to a storage/DB adapter API (e.g. `parsedTableName`, `parsedColumn`, `parsedIndexName` callers) containing hyphens, dots, spaces, unicode, quotes, or a name longer than 63 chars; dynamic table names derived from user input or environment values like a prefixed `MASTRA_*` scope.","commonSituations":"Naming storage tables after tenant/product slugs with dashes (`my-app_agents`); auto-generating index names that exceed 63 chars due to long prefixes; interpolated table names from env vars containing invalid characters.","solutions":["Rename the identifier to match `[A-Za-z_][A-Za-z0-9_]{0,62}` — replace dashes/dots/spaces with underscores.","Truncate or hash long names to stay under 63 characters (e.g. prefix + short hash).","Sanitize dynamic names before calling the API: `name.replace(/[^A-Za-z0-9_]/g, '_')` and assert length.","Use Mastra's configuration options (e.g. table name prefixes) that produce compliant names instead of raw user input."],"exampleFix":"// before\nconst table = `mastra-${tenantSlug}_traces`; // dashes from slug\n// after\nconst table = `mastra_${tenantSlug.replace(/[^A-Za-z0-9_]/g, '_')}_traces`;","handlingStrategy":"validation","validationCode":"const SQL_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\nfunction assertSqlIdentifier(name: string, kind = 'identifier') {\n  if (!SQL_IDENTIFIER_PATTERN.test(name) || name.length > 63)\n    throw new Error(`Invalid ${kind}: ${name}`);\n}","typeGuard":"function isSqlIdentifier(name: string): name is `${string}` {\n  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && name.length <= 63;\n}","tryCatchPattern":"let table: SqlIdentifier;\ntry {\n  table = parseSqlIdentifier(rawTableName, 'table');\n} catch (e) {\n  throw new Error(`Configured table name \"${rawTableName}\" is invalid: ${(e as Error).message}`);\n}","preventionTips":["Derive dynamic table names with `raw.replace(/[^A-Za-z0-9_]/g, '_')` and cap length.","Avoid hyphens/dots/spaces in env-driven prefixes and tenant slugs used in table names.","Keep generated index names under 63 chars — truncate long prefixes and append a short hash."],"tags":["sql","validation","identifier","injection-prevention"],"backgroundTag":"invalid-sql-identifier","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}