{"record":{"id":"605b29dbcaced1e2","repo":"paperclipai/paperclip","slug":"every-ctx-db-parameter-must-be-referenced-by-a-n","errorCode":null,"errorMessage":"Every ctx.db parameter must be referenced by a $n placeholder","messagePattern":"Every ctx\\.db parameter must be referenced by a \\$n placeholder","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/plugin-database.ts","lineNumber":323,"sourceCode":"  if (params.length === 0) return sql.raw(statement);\n  const chunks: SQL[] = [];\n  let cursor = 0;\n  const placeholderPattern = /\\$(\\d+)/g;\n  const seen = new Set<number>();\n\n  for (const match of statement.matchAll(placeholderPattern)) {\n    const index = Number(match[1]);\n    if (!Number.isInteger(index) || index < 1 || index > params.length) {\n      throw new Error(`SQL placeholder $${match[1]} has no matching parameter`);\n    }\n    chunks.push(sql.raw(statement.slice(cursor, match.index)));\n    chunks.push(sql`${params[index - 1]}`);\n    seen.add(index);\n    cursor = match.index! + match[0].length;\n  }\n  chunks.push(sql.raw(statement.slice(cursor)));\n  if (seen.size !== params.length) {\n    throw new Error(\"Every ctx.db parameter must be referenced by a $n placeholder\");\n  }\n  return sql.join(chunks, sql.raw(\"\"));\n}\n\nasync function listSqlMigrationFiles(migrationsDir: string): Promise<string[]> {\n  const entries = await readdir(migrationsDir, { withFileTypes: true });\n  return entries\n    .filter((entry) => entry.isFile() && entry.name.endsWith(\".sql\"))\n    .map((entry) => entry.name)\n    .sort((a, b) => a.localeCompare(b));\n}\n\nfunction resolveMigrationsDir(packageRoot: string, migrationsDir: string): string {\n  const resolvedRoot = path.resolve(packageRoot);\n  const resolvedDir = path.resolve(resolvedRoot, migrationsDir);\n  const relative = path.relative(resolvedRoot, resolvedDir);\n  if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n    throw new Error(`Plugin migrationsDir escapes package root: ${migrationsDir}`);","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/server/src/services/plugin-database.ts#L305-L341","documentation":"Inverse binding guard in bindSql: after substituting placeholders, some supplied parameters were never referenced by any $n. Catches silently ignored parameters (usually a typo in placeholder numbering); the unused params array is at fault.","triggerScenarios":"Thrown at server/src/services/plugin-database.ts:323 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Reference every supplied parameter with a $n placeholder, or remove unused parameters."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}