{"record":{"id":"b0df19a875c48895","repo":"Budibase/budibase","slug":"operation-already-exists","errorCode":null,"errorMessage":"Operation already exists","messagePattern":"Operation already exists","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"warning","filePath":"packages/server/src/sdk/workspace/ai/agents/operations.ts","lineNumber":75,"sourceCode":"      normalizeOperationName(operation.name) === normalizedName\n    )\n  })\n\n  if (hasDuplicateName) {\n    throw new HTTPError(\n      `Operation with name '${operationName?.trim()}' already exists.`,\n      400\n    )\n  }\n}\n\nexport async function createOperation(\n  agentId: string,\n  operation: CreateAgentOperationInput\n): Promise<Agent> {\n  const existing = await getOrThrow(agentId)\n  if (existing.operations?.some(candidate => candidate.id === operation.id)) {\n    throw new HTTPError(\"Operation already exists\", 400)\n  }\n  assertUniqueOperationName(existing, operation.name)\n\n  return update({\n    ...existing,\n    operations: [\n      ...(existing.operations ?? []),\n      {\n        ...operation,\n        enabledTools: operation.enabledTools || [],\n      },\n    ],\n  })\n}\n\nexport async function updateOperation(\n  agentId: string,\n  operationId: string,","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/ai/agents/operations.ts#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"Double-submit of a 'create operation' request; retry logic replaying the same payload; importing an operation export into the same agent it came from.","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."],"exampleFix":"// before\nawait createOperation(agentId, { id: existingOpId, name: \"x\" }) // 400\n// after\nimport { v4 } from \"uuid\"\nawait createOperation(agentId, { id: v4(), name: \"x\" })","handlingStrategy":"validation","validationCode":"const idExists = agent.operations?.some(o => o.id === operation.id)\nif (idExists) {\n  return updateOperation(agentId, operation.id, operation) // or generate a new id\n}","typeGuard":"const isNewOperationId = (agent: Agent, operationId: string): boolean =>\n  !agent.operations?.some(o => o.id === operationId)","tryCatchPattern":"try {\n  await createOperation(agentId, operation)\n} catch (err) {\n  if (err instanceof HTTPError && err.status === 400 && err.message === \"Operation already exists\") {\n    // make idempotent: return existing or update instead\n  }\n}","preventionTips":["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."],"tags":["duplicate","http-400","agents"],"backgroundTag":"duplicate-id-conflict","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}