Summary
gddy dns set, when the target type+name already has at least one record, deletes the entire DNS zone — every A/AAAA/CNAME/MX/TXT/SRV record is removed, leaving only the GoDaddy-managed system records (NS, SOA) plus _domainconnect.
The reconcile step issues a PUT /v3/domains/zones/{zone}/dns-records/{recordId}. That endpoint does not exist in the v3 API (the OpenAPI spec at https://developer.godaddy.com/openapi/domains-v3.json defines only GET, POST, and DELETE /{recordId} for dns-records — there is no PUT). The server nonetheless returns 200 and appears to treat the request as a replace of the whole record collection with the single record carried in the body, so the rest of the zone is discarded.
--dry-run does not help: for dns set it is a no-op stub that makes no API calls and prints {"action":"dry-run: would execute"}, so it never reveals the blast radius.
Version: gddy 0.1.10 (commit a2fc6d8ba2de, built 2026-07-17)
Steps to reproduce
On a zone that has several records, target a type+name that already holds ≥1 record:
# seed a name with two A records
gddy dns add --type A --name foo --data 5.5.5.5 example.com
gddy dns add --type A --name foo --data 6.6.6.6 example.com
# now "replace" them with a single value
gddy dns set --type A --name foo --data 7.7.7.7 example.com
Expected: only the two foo A records are reconciled to foo → 7.7.7.7; every other record in the zone is untouched.
Actual: the whole zone collapses to just the system records (NS, SOA, _domainconnect). All unrelated A/CNAME/MX/TXT/SRV records are gone.
HTTP trace (--debug transport)
> GET https://api.godaddy.com/v3/domains/zones/example.com/dns-records?name=foo&page=1&pageSize=100&totalRequired=true&type=A
< 200 # returns the 2 existing records
> PUT https://api.godaddy.com/v3/domains/zones/example.com/dns-records/{recordId}
> {"data":"7.7.7.7","name":"foo","ttl":3600,"type":"A"}
< 200 # <-- phantom endpoint; zone gets wiped here
> DELETE https://api.godaddy.com/v3/domains/zones/example.com/dns-records/{otherRecordId}
< 404 # RECORD_NOT_FOUND: ids were regenerated by the PUT
The reconcile for set reuses an existing record "in place" via PUT .../dns-records/{recordId}. Since v3 has no such route, the platform treats it as a collection-level replace using only the one record in the body.
Root cause
- The
dns set reconcile calls PUT /v3/domains/zones/{zone}/dns-records/{recordId} to update an existing record in place.
- v3
dns-records exposes only GET, POST, DELETE /{recordId} — no PUT. The API returns 200 but performs a destructive whole-collection replace, not a single-record update. The subsequent per-record DELETE then 404s because record IDs were regenerated.
Impact
High / data loss. A routine "point this record somewhere else" on any name that already exists destroys the entire zone. In our case this took down ~30 production frontends before it was understood. --dry-run gives a false sense of safety because it previews nothing for set.
Suggested fixes
- Stop using
PUT /dns-records/{recordId} for in-place updates — it is not a real v3 endpoint. Reconcile using only the documented verbs (POST to create, DELETE /{recordId} to remove).
- Make
dns set --dry-run actually fetch state and print the concrete plan (records to create/replace/delete), like a real preview.
- Until fixed, consider documenting
dns set as unsafe for zones with existing records, and pointing users to add + delete.
Summary
gddy dns set, when the targettype+namealready has at least one record, deletes the entire DNS zone — every A/AAAA/CNAME/MX/TXT/SRV record is removed, leaving only the GoDaddy-managed system records (NS, SOA) plus_domainconnect.The reconcile step issues a
PUT /v3/domains/zones/{zone}/dns-records/{recordId}. That endpoint does not exist in the v3 API (the OpenAPI spec athttps://developer.godaddy.com/openapi/domains-v3.jsondefines onlyGET,POST, andDELETE /{recordId}fordns-records— there is no PUT). The server nonetheless returns200and appears to treat the request as a replace of the whole record collection with the single record carried in the body, so the rest of the zone is discarded.--dry-rundoes not help: fordns setit is a no-op stub that makes no API calls and prints{"action":"dry-run: would execute"}, so it never reveals the blast radius.Version:
gddy 0.1.10 (commit a2fc6d8ba2de, built 2026-07-17)Steps to reproduce
On a zone that has several records, target a
type+namethat already holds ≥1 record:Expected: only the two
fooA records are reconciled tofoo → 7.7.7.7; every other record in the zone is untouched.Actual: the whole zone collapses to just the system records (NS, SOA,
_domainconnect). All unrelated A/CNAME/MX/TXT/SRV records are gone.HTTP trace (
--debug transport)The reconcile for
setreuses an existing record "in place" viaPUT .../dns-records/{recordId}. Since v3 has no such route, the platform treats it as a collection-level replace using only the one record in the body.Root cause
dns setreconcile callsPUT /v3/domains/zones/{zone}/dns-records/{recordId}to update an existing record in place.dns-recordsexposes onlyGET,POST,DELETE /{recordId}— noPUT. The API returns200but performs a destructive whole-collection replace, not a single-record update. The subsequent per-recordDELETEthen404s because record IDs were regenerated.Impact
High / data loss. A routine "point this record somewhere else" on any name that already exists destroys the entire zone. In our case this took down ~30 production frontends before it was understood.
--dry-rungives a false sense of safety because it previews nothing forset.Suggested fixes
PUT /dns-records/{recordId}for in-place updates — it is not a real v3 endpoint. Reconcile using only the documented verbs (POSTto create,DELETE /{recordId}to remove).dns set --dry-runactually fetch state and print the concrete plan (records to create/replace/delete), like a real preview.dns setas unsafe for zones with existing records, and pointing users toadd+delete.