# When Your AI Duplicates Every Field: A Story About Idempotency

> Source: <https://dev.to/keofung/when-your-ai-duplicates-every-field-a-story-about-idempotency-3jj6>
> Published: 2026-07-24 08:55:00+00:00

I had a form with 12 fields. Then my Claude Desktop session crashed. I restarted it, asked Claude to "continue building the form," and ended up with 24 fields. Half of them were exact duplicates — same IDs, same titles, same types. The form rendered with two "Email" fields, two "Name" fields, and two of every radio button.

This is the story of how `field_add`

isn't idempotent, why that matters when an AI is driving, and the pattern I adopted to fix it.

Update (v0.2.0): The`field_add`

command is now accessed via`formlm_exec`

. The`formlm_generate`

smart pipeline eliminates this entire class of problem — the server-side AssessAgent handles field creation atomically and idempotently. The find-before-add pattern remains useful when using`formlm_exec`

for manual field operations.

Here's the sequence:

`app_list`

, found the existing app, and started adding fields againThe result was a form with 20 fields total (8 original + 12 duplicate). The duplicate field IDs were auto-suffixed by the server (`email`

became `email`

, `email_1`

, `email_2`

...). To the user filling out the form, it looked like the form had been designed by someone who really, really wanted your email address.

`field_add`

Isn't Idempotent
Here's the thing about `field_add`

— it's a create operation, not an upsert. If you call it twice with the same field ID, the server creates two fields. The field ID isn't a unique constraint; it's a label.

Looking at the MCP tool definition:

```
server.tool('field_add', 'Add a new field', {
  appId: z.string().describe('App ID'),
  id: z.string().describe('Field ID'),
  type: z.string().default('input').describe('Field type (default: input)'),
  title: z.string().optional().describe('Field title / question text'),
  // ...
}, async (params) => {
  let cmd = `assess form add --app ${params.appId} --id ${params.id} --type ${params.type}`;
  // ...
});
```

There's no check for "does this field ID already exist?" The tool just sends the add command and the server creates a new field. If a field with that ID already exists, the server either appends a suffix or creates a duplicate.

From a CLI perspective, this is fine. A human running `formlm-cli field add`

knows whether they've already added a field. They don't need the CLI to check for them.

From an AI perspective, this is a problem. The AI doesn't remember what it did in a previous session. It doesn't check whether fields exist before adding them. It just executes the plan, and if the plan says "add 12 fields," it adds 12 fields — regardless of whether 8 of them are already there.

The pattern I now enforce is: **always check before adding**. Before calling `field_add`

, call `field_find`

to see if a field with that ID already exists:

```
# Step 1: Check if the field already exists
field_find --app <appId> --id email

# If the response is "field not found", proceed with add:
field_add --app <appId> --id email --type input --title "Email" --required

# If the field already exists, skip the add (or update it instead)
```

In practice, I instruct Claude to batch this: call `field_list`

once to get all existing fields, then only add the ones that are missing.

```
# Get the current state
field_list --app <appId>
# Response: [name, email, phone, rating]

# Claude now knows which fields exist and only adds the missing ones
# It needs to add: comments, feedback_source
field_add --app <appId> --id comments --type textarea --title "Additional comments"
field_add --app <appId> --id feedback_source --type radio --options "Search:1,Social:2,Friend:3"
```

This is the read-before-write pattern again (same as find-then-set from the previous article), but applied to creation instead of updates.

`field_add`

Idempotent?
I considered making `field_add`

an upsert — if the field exists, update it; if not, create it. But that has its own problems:

**Silent overwrites.** If the AI accidentally re-adds a field with different options, the upsert would silently overwrite the existing field's options. No error, no warning — just changed data.

**No explicit "create" semantics.** Sometimes you want to fail if a field already exists — like when you're setting up a new form and a duplicate field ID indicates a bug in your plan.

**The update path is different.** Creating a field and updating a field go through different server-side validation. Merging them into one operation muddies the API.

So I kept `field_add`

as a pure create operation and moved the idempotency check to the caller. The AI is responsible for checking before adding.

In my system prompt for Claude, I now include this instruction:

"Before adding any field, call field_list to get the current field set. Only add fields whose IDs don't already exist in the form. If a field already exists and needs changes, use field_update or field_set_property instead of field_add."

This is a soft constraint — it relies on Claude reading and following the instruction. In practice, it works about 90% of the time. The other 10%, Claude skips the `field_list`

call and goes straight to `field_add`

, usually when it's "in a hurry" (i.e., the context window is getting full and it's trying to save tokens).

The hard constraint would be making the server reject duplicate field IDs. But as I mentioned, field IDs aren't unique constraints — they're labels. Making them unique would break existing forms that intentionally use the same ID across different field types (don't ask).

This isn't just about `field_add`

. Every create operation in an AI-accessible API needs an idempotency story:

`app_create`

`share_publish`

`auth_login`

The pattern I now follow for any create operation:

Cleaning up the duplicated form was painful. I had to:

`field_list`

to see all 20 fields`email_1`

, `email_2`

)`field_remove`

for each duplicate`field_move`

Twelve `field_remove`

calls. Each one was a round-trip to the server. It took longer to clean up than it took to create the mess.

After that experience, I wrote a cleanup script that detects and removes duplicate fields by comparing titles and types. It's not part of the CLI — it's a one-off shell script I keep in my snippets folder. If this happens often enough, I might add a `field_dedup`

command to the CLI. But for now, the find-before-add pattern is enough.

*The formlm-cli MCP server documents the find-before-add pattern in tool descriptions. Open source at github.com/formlm/cli — try the platform at formlm.me.*
