Now I have a complete diagnosis. Here's the summary of all issues:
Diagnosis
Root Cause 1: "0.0.0.0" treated as valid IP (Issues #55, #53, #61)
In machine/subcommands/create.py:87:
ip_address = vm.ip_address # Returns "0.0.0.0" on Vultr
if (wait_for_ip or update_dns) and not ip_address: # "0.0.0.0" is truthy → skips wait!
Vultr newly-created instances return main_ip: "0.0.0.0" while status: "pending". Since "0.0.0.0" is a non-empty string, not ip_address is False, so the wait-for-IP loop is never entered. The DNS record then gets created with 0.0.0.0.
The list-domain command then can't match DNS records (data 0.0.0.0) to VMs (which eventually get real IPs), so the DNS test fails.
Root Cause 2: Can't destroy pending Vultr instances (Issue #57)
Vultr returns HTTP 500 when you try to destroy an instance that is still in pending status: "This subscription is not currently active, you cannot destroy it."
Because the create command returns almost instantly (the 0.0.0.0 IP skips the wait), the tests complete in ~9 seconds. The teardown fixture then tries to destroy the VM while it's still pending, which fails. The destroy_vm call in vultr.py catches the VultrException via fatal_error() which exits the subprocess, but the test teardown ignores the subprocess return code.
Fixes needed:
create.py: Treat "0.0.0.0" as "no IP yet" in the wait loop
vultr.py: In destroy_vm, wait for the instance to become active before attempting deletion (or retry)
test_e2e.py: test_instance_has_ip should validate the IP is not 0.0.0.0
Want me to implement these fixes?