cd /news/developer-tools/when-your-ai-duplicates-every-field-… · home topics developer-tools article
[ARTICLE · art-71681] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

When Your AI Duplicates Every Field: A Story About Idempotency

A developer building a form with Claude Desktop encountered a bug where the AI duplicated every field after a session crash, resulting in 24 fields instead of 12. The root cause is that the `field_add` command is not idempotent, so the AI re-added fields without checking for existing ones. The developer fixed it by enforcing a read-before-write pattern: always check if a field exists before adding it.

read5 min views1 publishedJul 24, 2026

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): Thefield_add

command is now accessed viaformlm_exec

. Theformlm_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 usingformlm_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:

field_find --app <appId> --id email

field_add --app <appId> --id email --type input --title "Email" --required

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.

field_list --app <appId>

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 fieldsemail_1

, email_2

)field_remove

for each duplicatefield_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.

── more in #developer-tools 4 stories · sorted by recency
── more on @claude desktop 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/when-your-ai-duplica…] indexed:0 read:5min 2026-07-24 ·