{"record":{"id":"596c9802e7a74859","repo":"can1357/oh-my-pi","slug":"sqlsessionstorage-table-name-must-match-ident-r","errorCode":null,"errorMessage":"SqlSessionStorage: table name must match ${IDENT_RE.source} (got ${JSON.stringify(table)})","messagePattern":"SqlSessionStorage: table name must match (.+?) \\(got (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/sql-session-storage.ts","lineNumber":278,"sourceCode":"\n\tget table(): string {\n\t\treturn this.#table;\n\t}\n}\n\nclass SqlSessionStorageBackend implements SessionStorageBackend {\n\treadonly #client: SqlSessionStorageClient;\n\treadonly #adapter: SqlSessionStorageAdapter;\n\treadonly #table: string;\n\treadonly #q: DialectQueries;\n\treadonly #createTable: boolean;\n\n\tconstructor(options: SqlSessionStorageOptions) {\n\t\tthis.#client = options.client;\n\t\tthis.#adapter = options.adapter ?? detectAdapter(options.client);\n\t\tconst table = options.table ?? DEFAULT_TABLE;\n\t\tif (!IDENT_RE.test(table)) {\n\t\t\tthrow new Error(`SqlSessionStorage: table name must match ${IDENT_RE.source} (got ${JSON.stringify(table)})`);\n\t\t}\n\t\tthis.#table = table;\n\t\tthis.#q = buildQueries(this.#adapter, table);\n\t\tthis.#createTable = options.createTable !== false;\n\t}\n\n\tget adapter(): SqlSessionStorageAdapter {\n\t\treturn this.#adapter;\n\t}\n\n\tget table(): string {\n\t\treturn this.#table;\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.#createTable) {\n\t\t\tawait this.#client.unsafe(this.#q.createTable);\n\t\t\tfor (const query of this.#q.addTitleColumns) {","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/sql-session-storage.ts#L260-L296","documentation":"SqlSessionStorage interpolates the table name directly into generated SQL, so it validates it against IDENT_RE (identifier-safe characters only) in the constructor and throws if it fails. This prevents SQL injection and syntax errors from table names containing quotes, dashes, spaces, or dots.","triggerScenarios":"new SqlSessionStorage({ ... , table: '<name>' }) where the name fails IDENT_RE — e.g. contains '-', '.', spaces, quotes, or is empty (sql-session-storage.ts:276-279).","commonSituations":"Deriving the table name from a filename, environment variable, or tenant ID that contains hyphens or dots ('my-sessions', 'omp.sessions'); copy-pasting a qualified name like 'public.sessions'; an empty-string config value.","solutions":["Use a plain identifier: letters, digits, underscores only, e.g. 'sessions' or 'omp_sessions_v2'.","Sanitize derived names before passing: replace invalid chars with '_' and ensure it doesn't start with a digit if IDENT_RE requires that.","Omit the option entirely to use DEFAULT_TABLE.","If you need schema-qualified names, connect with a search_path/connection default schema instead."],"exampleFix":"// before\nnew SqlSessionStorage({ client, adapter: 'postgres', table: 'omp.sessions' });\n// after\nnew SqlSessionStorage({ client, adapter: 'postgres', table: 'omp_sessions' });","handlingStrategy":"validation","validationCode":"const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/; // mirror of the library check\nif (!IDENT_RE.test(tableName)) {\n  throw new Error(`table name must be a plain identifier, got: ${tableName}`);\n}","typeGuard":"function isValidTableName(t: string): boolean {\n  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(t);\n}","tryCatchPattern":"try {\n  storage = new SqlSessionStorage({ client, adapter, table });\n} catch (err) {\n  if (String(err.message).includes('table name must match')) {\n    table = tableName.replace(/[^A-Za-z0-9_]/g, '_');\n    storage = new SqlSessionStorage({ client, adapter, table });\n  } else throw err;\n}","preventionTips":["Build table names only from [A-Za-z0-9_], starting with a letter or underscore.","Never pass filenames, tenant IDs, or schema-qualified names directly as the table option.","Sanitize dynamically derived names before constructing the storage."],"tags":["sql","validation","table-name","injection-prevention"],"backgroundTag":"invalid-identifier-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}