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
- Generate a fresh unique id (uuid) for each new operation instead of reusing IDs.
- Check existing.operations for the id before calling createOperation; if present, treat it as idempotent success or use updateOperation instead.
- 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
- Generate IDs with uuid/v4 at creation time; never reuse request payloads on retry.
- Implement client-side idempotency: on duplicate-ID 400, fetch and update instead.
- Strip operation IDs when importing operations into a different agent.
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
- Agent with name '${name}' already exists.
- Operation with name '${operationName?.trim()}' already exist
- Agent not found
- Operation not found
- Invalid bookmark query
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/b0df19a875c48895.
Report an issue: GitHub.