Budibase/budibase · warning · HTTPError

Operation with name '${operationName?.trim()}' already exist

Error message

Operation with name '${operationName?.trim()}' already exists.

What it means

assertUniqueOperationName enforces unique operation names per agent, case-insensitively (trimmed, lowercased). When another operation on the same agent (excluding the one being updated) normalizes to the same name, it throws a 400 HTTPError naming the conflicting name.

Source

Thrown at packages/server/src/sdk/workspace/ai/agents/operations.ts:62

const assertUniqueOperationName = (
  agent: Agent,
  operationName: string | undefined,
  excludedOperationId?: string
) => {
  const normalizedName = normalizeOperationName(operationName)
  if (!normalizedName) {
    return
  }

  const hasDuplicateName = (agent.operations ?? []).some(operation => {
    return (
      operation.id !== excludedOperationId &&
      normalizeOperationName(operation.name) === normalizedName
    )
  })

  if (hasDuplicateName) {
    throw new HTTPError(
      `Operation with name '${operationName?.trim()}' already exists.`,
      400
    )
  }
}

export async function createOperation(
  agentId: string,
  operation: CreateAgentOperationInput
): Promise<Agent> {
  const existing = await getOrThrow(agentId)
  if (existing.operations?.some(candidate => candidate.id === operation.id)) {
    throw new HTTPError("Operation already exists", 400)
  }
  assertUniqueOperationName(existing, operation.name)

  return update({
    ...existing,

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Check the agent's existing operation names (normalized) before create/rename and pick a unique name.
  2. If updating, ensure the name check excludes only the operation being edited - rename to something not used by others.
  3. Catch the 400 HTTPError and show a 'name already in use' validation message to the user.

Example fix

// before
await updateOperation(agentId, opId, { name: existingName }) // 400
// after
const name = "Run Report 2"
const clash = agent.operations?.some(o => o.id !== opId && o.name?.trim().toLowerCase() === name.trim().toLowerCase())
if (clash) throw new HTTPError("Name in use", 400)
await updateOperation(agentId, opId, { name })
Defensive patterns

Strategy: validation

Validate before calling

const norm = (n?: string) => n?.trim().toLowerCase() || ""
const isNameFree = (agent: Agent, name: string, excludeId?: string) =>
  !(agent.operations ?? []).some(o => o.id !== excludeId && norm(o.name) === norm(name))
if (!isNameFree(agent, newName, opId)) {
  throw new HTTPError("Name already in use", 400)
}

Type guard

const isUniqueName = (agent: Agent, name: string, excludeId?: string): boolean =>
  !(agent.operations ?? []).some(
    o => o.id !== excludeId && o.name?.trim().toLowerCase() === name.trim().toLowerCase()
  )

Try / catch

try {
  await createOperation(agentId, { id, name })
} catch (err) {
  if (err instanceof HTTPError && err.status === 400 && err.message.includes("already exists")) {
    // prompt user for a different name
  }
}

Prevention

When it happens

Trigger: createOperation with a name that collides with an existing operation on the agent, or updateOperation renaming an operation to a name already used by a sibling operation (case/whitespace differences still collide).

Common situations: Users renaming 'Run Report' to 'run report' which clashes with an existing 'Run Report'; bulk-importing operations with duplicate labels; API integrations generating non-unique names.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/48737b62c365f7f6. Report an issue: GitHub.