TXT records are write-once: the txt registry cannot map a TXT ownership record back, so updates and deletes are silently dropped

Author: berkkulaksizCreated Sep 10, 2026Updated Sep 12, 2026

What happened:

external-dns creates a TXT record, then can never update or delete it. The failure is silent — the plan produces the update, the registry discards it, and nothing is logged above --log-level=debug.

registry/txt.go:

go
func getSupportedTypes() []string {
	return []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS, endpoint.RecordTypeMX}
}

TXT is missing. The write path names the ownership record txt-<name> unconditionally (affixNameMapper.toTXTName, which builds recordType + "-" for any type), but the read path (extractRecordTypeDefaultPosition) only strips a leading <type>- when <type> is in getSupportedTypes(). So txt-_spf.example.com maps back to ("txt-_spf.example.com", "") instead of ("_spf.example.com", "TXT"). The label map is keyed on the wrong entry, the owner label is never re-attached to the data record, and TXTRegistry.ApplyChanges drops the change through FilterEndpointsByOwnerID.

Creates are unaffected, because filteredChanges.Create is not passed through that filter. That is what makes this quiet in practice: every new TXT name works, and only a changed value reveals the problem.

Debug evidence from a live cluster and zone (domain kept, it is ours). Desired comes from a DNSEndpoint; the record was created by external-dns itself a day earlier and its ownership record is present and well-formed:

Skipping endpoint _spf.microframe.co 300 IN TXT  v=spf1 ip4:91.98.39.147 ip4:167.233.235.154 … -all […] because of missing owner label (required: "bytegurus-k8s-crd")
Skipping endpoint _spf.microframe.co 300 IN TXT  v=spf1 ip4:167.233.235.154 -all […] because owner id does not match (found: "", required: "bytegurus-k8s-crd")

The first line is UpdateOld (current), the second UpdateNew (desired, which inherited the empty owner from current). The ownership record exists in the zone and parses:

txt-_spf.microframe.co  TXT  "heritage=external-dns,external-dns/owner=bytegurus-k8s-crd,external-dns/resource=crd/external-dns/…"

What you expected to happen:

A TXT record external-dns created and owns is updated when the desired value changes, and removed when it leaves the source — the same as an A or CNAME record.

How to reproduce it (as minimally and precisely as possible):

  1. Run with --registry=txt, no --txt-prefix/--txt-suffix, --managed-record-types including TXT, --policy=sync, --source=crd.
  2. Create a DNSEndpoint with a TXT record, e.g. _spf.example.com"v=spf1 ip4:198.51.100.10 -all". It is published correctly, with an ownership record at txt-_spf.example.com.
  3. Change the target, e.g. to "v=spf1 ip4:203.0.113.10 -all".
  4. Nothing happens. With --log-level=debug the two "Skipping endpoint" lines above appear on every interval; at info level there is no output at all.
  5. Removing the endpoint from the source does not delete the record either.

Anything else we need to know?:

  • #6293 (open since 2026-03-18, last touched 2026-06-14) fixes exactly this — it adds TXT to the mapper's supported records and requires %{record_type} in the affix when TXT is managed. Filing this so the behaviour itself is tracked; the PR has been open for six months and there is no issue describing what it costs.
  • The practical cost is worth stating, because the silence is the dangerous part. On a mail domain this means a DKIM key cannot be rotated, DMARC cannot be moved off p=none, and SPF cannot be tightened from ~all to -all: each of those is a value change on an existing TXT name, each one is accepted in the source, and none of them reach DNS. We found it only because an SPF record kept authorising five decommissioned addresses for a day after the source said one.
  • Reproduced on v0.20.0 with the Cloudflare provider, but nothing here is provider-specific — the drop happens in the registry, before the provider is asked to do anything.
  • A workaround for a single record is to publish it outside external-dns; there is no workaround that keeps it managed, since adding a prefix without the fix in #6293 does not restore the reverse mapping either.

Environment:

  • External-DNS version: v0.20.0 (registry.k8s.io/external-dns/external-dns:v0.20.0)
  • DNS provider: cloudflare
  • Others: --registry=txt, no txt prefix or suffix, --policy=sync, --source=crd, --managed-record-types=A,AAAA,CNAME,MX,TXT, k3s v1.35.1

Transparency note: this was diagnosed and the report drafted with the help of an AI assistant (Claude). Every observation — the missing owner label, the two debug lines, the well-formed ownership record, and the fact that creates still work — was reproduced against a real cluster and a real zone by the human operator.

Source: kubernetes-sigs/external-dns