windmill-labs/windmill · error

Workspace already has datatable(s): ${existing.map((d) => d.

Error message

Workspace already has datatable(s): ${existing.map((d) => d.name).join(", ")}. Re-run with --force to add '${dtName}' (note: fork metadata on existing datatables is not preserved)

What it means

`wmill datatable create` also refuses to touch a workspace that already has ANY datatables unless `--force` is passed, because the create path rewrites the workspace datatable config wholesale and fork metadata on existing datatables cannot be read back through the API. This guards against silently losing per-datatable settings when adding a datatable to a non-empty workspace.

Source

Thrown at cli/src/commands/datatable/datatable.ts:132

async function create(
  opts: GlobalOptions & { resource?: string; force?: boolean },
  name?: string,
) {
  const workspace = await resolveWorkspace(opts);
  await requireLogin(opts);
  const dtName = name ?? DEFAULT_DATATABLE_NAME;

  const existing = await wmill.listDataTables({
    workspace: workspace.workspaceId,
  });
  if (existing.some((d) => d.name === dtName)) {
    throw new Error(`Datatable '${dtName}' already exists in this workspace`);
  }
  // edit_datatable_config replaces the whole settings object, and fork
  // metadata on existing datatables can't be read back through the API —
  // so only touch a non-empty config when explicitly asked to.
  if (existing.length > 0 && !opts.force) {
    throw new Error(
      `Workspace already has datatable(s): ${existing
        .map((d) => d.name)
        .join(", ")}. Re-run with --force to add '${dtName}' ` +
        "(note: fork metadata on existing datatables is not preserved)",
    );
  }

  const datatables: Record<
    string,
    { database: { resource_type: "postgresql" | "instance"; resource_path?: string } }
  > = {};
  for (const d of existing) {
    datatables[d.name] = {
      database: {
        resource_type: d.resource_type as "postgresql" | "instance",
        resource_path: d.resource_path ?? undefined,
      },
    };

View on GitHub (pinned to e474e8803c)

Solutions

  1. Re-run with `--force`: `wmill datatable create <name> --force` (accepting that fork metadata on existing datatables is not preserved).
  2. Create the datatable in a fresh/empty workspace if preserving metadata matters.
  3. Recreate the fork metadata manually after a forced create, if unavoidable.

Example fix

// before
wmill datatable create analytics
// after
wmill datatable create analytics --force
Defensive patterns

Strategy: validation

Validate before calling

const existing = await wmill.listDataTables({ workspace });
if (existing.length > 0 && !process.env.FORCE_DT) throw new Error('workspace not empty; pass --force intentionally');

Type guard

null

Try / catch

try { await create() } catch (e) { if (String(e).includes('Re-run with --force')) promptUserForForce(); else throw e; }

Prevention

When it happens

Trigger: Running `wmill datatable create <name>` in a workspace where `listDataTables` returns at least one datatable (and the requested name itself doesn't collide) without the `--force` flag.

Common situations: Adding a second datatable to a shared workspace that was set up by `wmill datatable init`-style tooling; workspaces whose datatables carry fork metadata that a blind config rewrite would destroy.

Related errors


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