{"record":{"id":"4601d45c40bdc482","repo":"windmill-labs/windmill","slug":"invalid-migration-name-name-use-only-letters","errorCode":null,"errorMessage":"Invalid migration name '${name}': use only letters, digits, '_' and '-'","messagePattern":"Invalid migration name '(.+?)': use only letters, digits, '_' and '-'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/commands/datatable_migrations.ts","lineNumber":53,"sourceCode":"function nextMigrationTimestamp(dir: string): string {\n  const now = Number(migrationTimestamp());\n  let max = 0;\n  if (fs.existsSync(dir)) {\n    for (const file of fs.readdirSync(dir)) {\n      const m = file.match(/^(\\d+)_.*\\.(up|down)\\.sql$/);\n      if (m) max = Math.max(max, Number(m[1]));\n    }\n  }\n  return String(max >= now ? max + 1 : now);\n}\n\n/**\n * Scaffold a new migration under migrations/datatable/<datatable>/ as empty\n * `<timestamp>_<name>.up.sql` and `.down.sql` files. Purely local — no network.\n */\nexport function createMigration(datatable: string, name: string): void {\n  if (!MIGRATION_NAME_RE.test(name)) {\n    throw new Error(\n      `Invalid migration name '${name}': use only letters, digits, '_' and '-'`,\n    );\n  }\n  const dir = path.join(process.cwd(), MIGRATIONS_DIR, datatable);\n  fs.mkdirSync(dir, { recursive: true });\n\n  const timestamp = nextMigrationTimestamp(dir);\n  const base = `${timestamp}_${name}`;\n  const up = path.join(dir, `${base}.up.sql`);\n  const down = path.join(dir, `${base}.down.sql`);\n  // Frame the body in an explicit transaction so it applies atomically, matching\n  // the template the UI's \"New migration\" modal seeds.\n  const template = (direction: string) =>\n    `-- ${direction} migration: ${name}\\nBEGIN;\\n\\n-- Add your migration here\\n\\nEND;\\n`;\n  fs.writeFileSync(up, template(\"up\"), \"utf-8\");\n  fs.writeFileSync(down, template(\"down\"), \"utf-8\");\n\n  log.info(","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/cli/src/commands/datatable_migrations.ts#L35-L71","documentation":"`createMigration` scaffolds empty `<timestamp>_<name>.up.sql`/`.down.sql` files for a datatable migration. It validates the name against a regex allowing only letters, digits, underscore and hyphen, and throws this error for anything else (spaces, dots, slashes, etc.), preventing invalid filenames or path traversal.","triggerScenarios":"Running `wmill datatable migrate new <datatable> <name>` where `<name>` contains characters outside [A-Za-z0-9_-] — e.g. spaces, dots ('1.2.3'), slashes, or shell-expanded glob characters.","commonSituations":"Copy-pasting a description with spaces as the migration name; using dotted version numbers; accidentally passing a file path as the name.","solutions":["Rename the migration using only letters, digits, '_' and '-', e.g. `add-users-table`.","Quote the argument in your shell to avoid word-splitting, then replace offending characters.","Use kebab-case or snake_case names going forward."],"exampleFix":"// before\nwmill datatable migrate new main add users table\n// after\nwmill datatable migrate new main add-users-table","handlingStrategy":"validation","validationCode":"const RE = /^[A-Za-z0-9_-]+$/;\nif (!RE.test(name)) throw new Error(`migration name '${name}' must match [A-Za-z0-9_-]`);","typeGuard":"function isValidMigrationName(n: string): boolean { return /^[A-Za-z0-9_-]+$/.test(n); }","tryCatchPattern":"try { createMigration(dt, name) } catch (e) { if (String(e).includes('Invalid migration name')) name = name.replace(/[^A-Za-z0-9_-]/g, '-'); }","preventionTips":["Use kebab-case or snake_case migration names","Quote arguments in shell to avoid splitting","Sanitize names derived from ticket titles/descriptions before passing"],"tags":["cli","validation","migrations","naming"],"backgroundTag":"invalid-name-validation","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}