windmill-labs/windmill · error · Error

Fork workspace id \`${id}\` is invalid: ${reason}. Choose a

Error message

Fork workspace id \`${id}\` is invalid: ${reason}. Choose a shorter or simpler name/id.

What it means

`validateForkWorkspaceId` pre-validates the generated `wm-fork-<slug>` workspace id (max 50 chars, simple slug) before any backend call. This early rejection exists because later steps (existsWorkspace, datatable cloning, branch creation) are not cleaned up on a late backend rejection.

Source

Thrown at cli/src/commands/workspace/fork.ts:500

 */
function branchToForkId(branch: string): string {
  const slug = branch
    .replace(/[^a-zA-Z0-9_-]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, MAX_FORK_ID_SLUG)
    .replace(/-+$/g, ""); // re-trim if the cut landed on a dash
  return slug || "fork";
}

/**
 * Mirror the backend `validate_fork_workspace_id` so an invalid id fails fast
 * — before `existsWorkspace`, datatable cloning (which creates real per-fork
 * Postgres databases), and branch creation, none of which get cleaned up on a
 * late backend rejection. `id` is the full `wm-fork-<slug>` workspace id.
 */
function validateForkWorkspaceId(id: string): void {
  const reject = (reason: string): never => {
    throw new Error(
      `Fork workspace id \`${id}\` is invalid: ${reason}. Choose a shorter or simpler name/id.`,
    );
  };
  if (id.length > 50) {
    reject(`too long (${id.length} chars; max 50 including the \`${WM_FORK_PREFIX}-\` prefix)`);
  }
  if (id.endsWith(".")) reject("cannot end with '.'");
  if (id.endsWith(".lock")) reject("cannot end with '.lock'");
  if (id.includes("..")) reject("cannot contain '..'");
  if (id.includes("@{")) reject("cannot contain '@{'");
  if (id.includes("//")) reject("cannot contain '//'");
  for (const ch of id) {
    if (":~^?*[\\ ".includes(ch)) reject(`contains forbidden character '${ch}'`);
    const code = ch.charCodeAt(0);
    if (code < 0x20 || code === 0x7f) reject("contains a control character");
  }
  for (const component of id.split("/")) {
    if (component.startsWith(".")) reject("a path component cannot start with '.'");

View on GitHub (pinned to e474e8803c)

Solutions

  1. Choose a shorter fork workspace name so `wm-fork-<name>` stays ≤ 50 chars
  2. Use only lowercase alphanumeric and dash characters in the name
  3. Re-run `wmill workspace fork` with the simplified id

Example fix

// before
wmill workspace fork --name my-extremely-long-experimental-feature-branch-name-2026
// after
wmill workspace fork --name exp-feature
Defensive patterns

Strategy: validation

Validate before calling

const id = `${WM_FORK_PREFIX}-${slug(name)}`;
if (id.length > 50 || !/^[a-z0-9-]+$/.test(id)) {
  throw new Error(`fork id ${id} invalid; shorten or simplify`);
}

Type guard

function isValidForkId(id: string): boolean {
  return id.length <= 50 && /^wm-fork-[a-z0-9-]+$/.test(id);
}

Try / catch

try {
  validateForkWorkspaceId(id);
} catch (e) {
  log.error(e.message); // includes the specific reason (too long, bad chars)
}

Prevention

When it happens

Trigger: The workspace name given to `wmill workspace fork` produces an id longer than 50 characters or containing characters outside the allowed slug set; `reject` is called with the specific reason.

Common situations: Long descriptive fork names like `wm-fork-my-very-long-experimental-feature-branch-2026` exceeding the limit; names with spaces or uppercase letters.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/d94d319659cf2943. Report an issue: GitHub.