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.