Budibase/budibase · warning · HTTPError

Operation already exists

Error message

Operation already exists

What it means

createOperation rejects creating an operation whose client-supplied id already exists on the agent, throwing a 400 HTTPError. Unlike the name check (which normalizes), this compares raw operation IDs, so it is a true duplicate-ID guard protecting per-agent operation ID uniqueness.

Source

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

      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,
    operations: [
      ...(existing.operations ?? []),
      {
        ...operation,
        enabledTools: operation.enabledTools || [],
      },
    ],
  })
}

export async function updateOperation(
  agentId: string,
  operationId: string,

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Generate a fresh unique id (uuid) for each new operation instead of reusing IDs.
  2. Check existing.operations for the id before calling createOperation; if present, treat it as idempotent success or use updateOperation instead.
  3. Catch the 400 HTTPError and fall back to updateOperation when the intent is to modify an existing operation.

Example fix

// before
await createOperation(agentId, { id: existingOpId, name: "x" }) // 400
// after
import { v4 } from "uuid"
await createOperation(agentId, { id: v4(), name: "x" })
Defensive patterns

Strategy: validation

Validate before calling

const idExists = agent.operations?.some(o => o.id === operation.id)
if (idExists) {
  return updateOperation(agentId, operation.id, operation) // or generate a new id
}

Type guard

const isNewOperationId = (agent: Agent, operationId: string): boolean =>
  !agent.operations?.some(o => o.id === operationId)

Try / catch

try {
  await createOperation(agentId, operation)
} catch (err) {
  if (err instanceof HTTPError && err.status === 400 && err.message === "Operation already exists") {
    // make idempotent: return existing or update instead
  }
}

Prevention

When it happens

Trigger: Calling createOperation(agentId, operation) where operation.id matches an existing operation.id on that agent - e.g. retrying a create after a timeout, or a client generating non-unique IDs.

Common situations: Double-submit of a 'create operation' request; retry logic replaying the same payload; importing an operation export into the same agent it came from.

Related errors


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