{"record":{"id":"21e507e39abd83fd","repo":"windmill-labs/windmill","slug":"invalid-datatable-migration-path-path","errorCode":null,"errorMessage":"Invalid datatable migration path: ${path}","messagePattern":"Invalid datatable migration path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/windmill-utils-internal/src/deploy.ts","lineNumber":331,"sourceCode":"  ];\n}\n\nfunction toError(e: unknown): string {\n  const err = e as { body?: string; message?: string };\n  return err.body || err.message || String(e);\n}\n\n// A datatable-migration diff item's path is `<datatable>/<timestamp>_<name>`\n// (mirrors the backend, e.g. `mydt/20260101000001_create_users`).\nexport function parseDatatableMigrationDeployPath(path: string): {\n  datatable: string;\n  timestamp: number;\n  name: string;\n} {\n  const slash = path.indexOf(\"/\");\n  const underscore = slash >= 0 ? path.indexOf(\"_\", slash + 1) : -1;\n  if (slash < 0 || underscore < 0) {\n    throw new Error(`Invalid datatable migration path: ${path}`);\n  }\n  const datatable = path.slice(0, slash);\n  const timestamp = Number(path.slice(slash + 1, underscore));\n  const name = path.slice(underscore + 1);\n  if (!datatable || !Number.isFinite(timestamp) || !name) {\n    throw new Error(`Invalid datatable migration path: ${path}`);\n  }\n  return { datatable, timestamp, name };\n}\n\n// The backend rejects `upsertDatatableMigration` when the target data table\n// hasn't opted in to migrations. Turn that opaque 400 into an explicit,\n// deploy-context message (falls back to the original error otherwise).\nfunction asMigrationsDisabledError(e: unknown, datatable: string): unknown {\n  const msg = (e as { body?: string; message?: string })?.body ?? ''\n  if (typeof msg === \"string\" && /migrations are not enabled/i.test(msg)) {\n    return new Error(\n      `Data table '${datatable}' has not opted in to migrations on the target workspace; enable migrations for it there before deploying its migrations.`","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/cli/windmill-utils-internal/src/deploy.ts#L313-L349","documentation":"parseDatatableMigrationDeployPath expects a datatable migration path shaped `<datatable>/<timestamp>_<name>` (e.g. `my_table/1726012345_create_users`). It throws when the path contains no `/` separating the datatable from the rest, or no `_` after the slash separating the timestamp from the migration name. This is a strict structural validation before the parts are sliced out.","triggerScenarios":"Calling parseDatatableMigrationDeployPath (via mergeWorkspaces or the `{datatable, timestamp}` caller) with a path missing `/` (bare filename like `1726012345_create_users`) or missing the `_` timestamp separator (like `my_table/1726012345create_users`).","commonSituations":"Hand-constructing migration file paths instead of using the CLI's scaffolding; renaming migration files and dropping the `timestamp_` prefix; a glob/scan picking up non-migration files inside the datatable directory; OS-specific path separators (`\\` on Windows).","solutions":["Rename the file to the required `<datatable>/<timestamp>_<name>` layout, e.g. `my_table/1726012345_create_users`","Ensure the timestamp segment is present and followed by `_`","Regenerate the migration with the CLI so the filename follows the convention","Check for backslash separators on Windows and use forward slashes"],"exampleFix":"// before\nparseDatatableMigrationDeployPath(\"1726012345_create_users\")   // no datatable/\nparseDatatableMigrationDeployPath(\"my_table/create_users\")    // no timestamp_\n\n// after\nparseDatatableMigrationDeployPath(\"my_table/1726012345_create_users\")","handlingStrategy":"validation","validationCode":"function isValidMigrationPath(path: string): boolean {\n  const m = /^([^/]+)\\/(\\d+)_([^/]+)$/.exec(path);\n  return m !== null && m[1] !== '' && m[3] !== '';\n}\nif (!isValidMigrationPath(p)) throw new Error(`Bad migration path: ${p}`);","typeGuard":"function isMigrationPath(p: string): p is `${string}/${number}_${string}` {\n  return /^[^/]+\\/\\d+_[^/]+$/.test(p);\n}","tryCatchPattern":"try {\n  const { datatable, timestamp, name } = parseDatatableMigrationDeployPath(p);\n} catch (e) {\n  console.error(`${p} must look like <datatable>/<timestamp>_<name>`);\n  process.exit(1);\n}","preventionTips":["Always scaffold migrations via the CLI rather than creating files by hand","Keep the `<datatable>/<timestamp>_<name>` filename convention; never rename the timestamp prefix","Use forward slashes; avoid OS-specific separators in stored paths","Glob only `*_migration`-style patterns to avoid picking up stray files"],"tags":["validation","naming","datatable"],"backgroundTag":"invalid-file-path-format","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"}