Skip to content

Commit ba304b8

Browse files
committed
warning(CLAUDE): Vapi PATCH on nested objects is REPLACE not deep-merge
On 2026-05-13 a Claude session wiped system prompts on three live-production iForm barge assistants by PATCHing the model object without including model.messages. The API replaced the entire model object — prompts gone, calls ran with empty system prompt until the operator forced a restore. Adds a prominent Critical Safety Rules section to the top of CLAUDE.md with: - The exact failure mode and incident reference - The mandatory GET-modify-PATCH-verify workflow with shell example - A list of all affected nested objects (model, voice, transcriber, etc.) - The 'patched X came back correct doesn't mean other fields survived' check - A recommendation to prefer the gitops engine over direct API PATCH
1 parent 23b5efe commit ba304b8

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,41 @@ This repository uses two instruction sources for Claude:
77

88
When both files exist, follow both. If guidance overlaps, treat `AGENTS.md` as the canonical project playbook and use this file to reinforce Claude-specific behavior.
99

10+
---
11+
12+
## ⚠️ CRITICAL SAFETY RULES — read before any direct Vapi API call
13+
14+
### Vapi PATCH on nested objects is REPLACE, not deep-merge
15+
16+
**The Vapi PATCH API does NOT deep-merge nested objects. When you PATCH a nested object (`model`, `voice`, `transcriber`, `messagePlan`, `analysisPlan`, `artifactPlan`, `voicemailDetection`, `startSpeakingPlan`, `stopSpeakingPlan`) with a partial body, the API REPLACES the entire object — wiping every field you didn't include.**
17+
18+
This wiped three live-production assistants' system prompts on 2026-05-13 (gitops-mudflap iForm barge fleet). The PATCH was `{"model": {"model": "gpt-4.1", "provider": "openai", "maxTokens": 260, "temperature": 0.3, "toolIds": [...]}}` — looked complete, but did NOT include `model.messages`. Result: prompts gone, live calls ran with empty system prompt until the operator forced a restore.
19+
20+
**Mandatory workflow for any direct API PATCH against a nested object:**
21+
22+
```bash
23+
# 1. GET the full resource first
24+
ASSISTANT=$(curl -H "Authorization: Bearer $VAPI_TOKEN" https://api.vapi.ai/assistant/$id)
25+
26+
# 2. Modify in place — keep every other field
27+
MODEL=$(echo "$ASSISTANT" | jq '.model | .model = "gpt-4.1"')
28+
29+
# 3. PATCH the COMPLETE nested object back
30+
curl -X PATCH -H "Content-Type: application/json" \
31+
-d "{\"model\": $MODEL}" \
32+
https://api.vapi.ai/assistant/$id
33+
34+
# 4. Re-GET and verify EVERY field you cared about — not just the one you changed
35+
```
36+
37+
**The "I patched X and X came back correct" check is NOT sufficient.** Vapi can replace the rest of the nested object even when X looks right in the response. Verify the fields you DIDN'T touch survived too — especially `model.messages` (system prompt), `model.toolIds`, `model.knowledgeBase`, and any nested config under `voice` / `transcriber`.
38+
39+
**When in doubt, use `npm run push -- <env>` instead of direct API PATCH.** The gitops engine constructs the full payload from local YAML automatically. Only fall back to direct curl PATCH when the engine is silently dropping specific fields (the 2026-04-26 `eagerEotThreshold` engine bug and the 2026-05-13 silent-push class). Even then, GET-modify-PATCH-verify.
40+
41+
See `docs/learnings/voice-providers.md` for related "property X should not exist" 400 gotchas (e.g. `voice.enableSsmlParsing` is rejected on `provider: vapi` voices) — those failures are loud; the PATCH-is-REPLACE failure is silent and far more dangerous.
42+
43+
---
44+
1045
## Required Reading Order
1146

1247
1. Read `AGENTS.md` first.

0 commit comments

Comments
 (0)