Post-MVP polish for the custom-domain CLI (#114). All non-blocking — samorev returned PASS on #114 — tracked for a follow-up pass.
- Orphaned CF hostname on SSH failure (
runDomainAdd): if the app-VM SSH step throws after CF create, the custom hostname exists on Cloudflare but no state record is written → domain rm can't find it. Fix: persist the DomainRecord immediately after a successful CF create (before the SSH step), or add domain repair <fqdn> (CF lookup by FQDN to recover the id).
- CF 409 → human-readable message (
runDomainAdd): a duplicate domain add surfaces the raw CF error; detect CloudflareError.status === 409 and emit "domain already registered on Cloudflare — run domain check <fqdn> to inspect status."
domain rm error message is aspirational: says "requires --yes (or interactive confirmation)" but defaultConfirm is never wired. Drop the "(or interactive confirmation)" clause, or wire it from destroy.ts.
vmIp format assertion in buildControlPlaneCustomDomainVhostScript — add a trivial IP-format check parallel to the existing isValidMainHost guards (defence-in-depth; not injectable in practice, source is the Hetzner API).
domain search exit code: always exits 0; consider exit 1 on "taken" for shell-idiomatic availability checks (--json already makes it scriptable).
Post-MVP polish for the custom-domain CLI (#114). All non-blocking — samorev returned PASS on #114 — tracked for a follow-up pass.
runDomainAdd): if the app-VM SSH step throws after CF create, the custom hostname exists on Cloudflare but no state record is written →domain rmcan't find it. Fix: persist theDomainRecordimmediately after a successful CF create (before the SSH step), or adddomain repair <fqdn>(CF lookup by FQDN to recover the id).runDomainAdd): a duplicatedomain addsurfaces the raw CF error; detectCloudflareError.status === 409and emit "domain already registered on Cloudflare — rundomain check <fqdn>to inspect status."domain rmerror message is aspirational: says "requires --yes (or interactive confirmation)" butdefaultConfirmis never wired. Drop the "(or interactive confirmation)" clause, or wire it fromdestroy.ts.vmIpformat assertion inbuildControlPlaneCustomDomainVhostScript— add a trivial IP-format check parallel to the existingisValidMainHostguards (defence-in-depth; not injectable in practice, source is the Hetzner API).domain searchexit code: always exits 0; considerexit 1on "taken" for shell-idiomatic availability checks (--jsonalready makes it scriptable).