Repository navigation
fix: available domains were always reported as taken - #2
Merged
Merged
Conversation
Checking an unregistered domain always returned "taken". Four separate defects contributed to this, all of which had to be fixed for an available domain to be detected correctly. 1. Duplicated /domain/ in RDAP URLs Most configured endpoints already end with /domain/, but the path segment was appended unconditionally, producing URLs such as https://rdap.verisign.com/com/v1/domain/domain/google.com which registries answer with HTTP 400. _build_query_url() now appends the segment only when the endpoint does not already end with it. Verified against all 193 entries of the TLD registry. 2. HTTP 404 with an empty body treated as an error A 404 was only accepted as "not found" if its body contained one of a few text markers. Verisign answers 404 with Content-Length: 0, so those responses became RDAPStatus.ERROR and therefore "taken". Per RFC 7480 Section 5.3 the status code is the defined signal and the error body is optional, so the status code is now authoritative. A 404 is only rejected if the body carries an RDAP error object whose errorCode contradicts it. 3. WHOIS "no match" signals that never matched The .com/.net signal was "No match for domain", while Verisign actually replies 'No match for "EXAMPLE.COM".'. The .org, .io and .biz signals were wrong too, so WHOIS confirmation returned AMBIGUOUS and the conservative logic fell back to "taken". Since .com has no secondary RDAP endpoint configured, WHOIS is the only confirming source, which is why fixing the URL alone was not enough. Signals corrected against the live registry responses. 4. Misleading confidence for "taken" Every "taken" result reported high confidence, including results that came only from errors, timeouts or missing confirmation. high is now reserved for results backed by an actual registration signal (RDAP FOUND or WHOIS FOUND); conservative fallbacks report low. Also fixed: unclassifiable 404 responses are no longer retried. They previously fell through to the generic server_error branch, causing four identical requests per domain against the registry. And __version__ was stuck at 0.1.0 while the package version was already 0.2.0, so --version reported the wrong release. The decision logic itself is unchanged: AVAILABLE still requires multi-source confirmation and any uncertainty still yields TAKEN. Verified end-to-end against the live registries: google.com -> taken / high (rdap_primary=found) hfupwefbabfdskf.com -> available / high (rdap_primary=not_found, whois=not_found) Both with 0 retries instead of 4 requests each. All 114 tests pass. Bumps version to 0.2.1. Fixes #1 Reported-by: @rkahv
json.loads() on a str can only raise JSONDecodeError (malformed JSON) or TypeError (wrong input type), so catching those explicitly is more precise than a blind except Exception. Also keeps the ruff findings for src/ at the same count as main (170), so this change does not add to the pre-existing lint debt.
The lint job started failing with 170 findings in src/ without any code change. Root cause: the project has no [tool.ruff] configuration, so ruff applies its own implicit default rule set, and the workflow installs ruff unpinned. Ruff expanded those defaults between releases. Verified with an identical sample file: ruff 0.8.6 (what CI resolved to in Dec 2025) -> All checks passed ruff 0.17.3 (what CI resolves to today) -> UP045, UP045, BLE001 So the job was green on main in December and is red now purely because of an upstream release. Two changes make the job deterministic again: - pyproject.toml declares select = ["E4", "E7", "E9", "F"], the rule set the project was developed and kept green against, so the rule set no longer depends on ruff's shifting defaults. - the workflow pins ruff==0.17.3 instead of resolving the latest release on every run. ruff check src/ now passes with 0 findings. None of the 170 suppressed findings were correctness bugs. The bulk was style modernization: 98x Optional[X] -> X | None, 21x blind except, 8x import sorting, 8x unnecessary pass, 7x datetime.UTC alias, plus smaller counts of deprecated-alias and simplification rules. Two are worth addressing separately on their own merits, not as drive-by changes in a bugfix PR: - ASYNC230: cli.py opens files with blocking open() inside async functions - S110: scheduler.py swallows task exceptions without logging them Adopting the broader rule set is a deliberate project decision and belongs in its own PR, where the ~170 mechanical edits can be reviewed on their own instead of burying the RDAP fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checking an unregistered domain always returned "taken". Four separate defects contributed to this, all of which had to be fixed for an available domain to be detected correctly.
Duplicated /domain/ in RDAP URLs
Most configured endpoints already end with /domain/, but the path segment was appended unconditionally, producing URLs such as https://rdap.verisign.com/com/v1/domain/domain/google.com which registries answer with HTTP 400. _build_query_url() now appends the segment only when the endpoint does not already end with it. Verified against all 193 entries of the TLD registry.
HTTP 404 with an empty body treated as an error
A 404 was only accepted as "not found" if its body contained one of a few text markers. Verisign answers 404 with Content-Length: 0, so those responses became RDAPStatus.ERROR and therefore "taken". Per RFC 7480 Section 5.3 the status code is the defined signal and the error body is optional, so the status code is now authoritative. A 404 is only rejected if the body carries an RDAP error object whose errorCode contradicts it.
WHOIS "no match" signals that never matched
The .com/.net signal was "No match for domain", while Verisign actually replies 'No match for "EXAMPLE.COM".'. The .org, .io and .biz signals were wrong too, so WHOIS confirmation returned AMBIGUOUS and the conservative logic fell back to "taken". Since .com has no secondary RDAP endpoint configured, WHOIS is the only confirming source, which is why fixing the URL alone was not enough. Signals corrected against the live registry responses.
Misleading confidence for "taken"
Every "taken" result reported high confidence, including results that came only from errors, timeouts or missing confirmation. high is now reserved for results backed by an actual registration signal (RDAP FOUND or WHOIS FOUND); conservative fallbacks report low.
Also fixed: unclassifiable 404 responses are no longer retried. They previously fell through to the generic server_error branch, causing four identical requests per domain against the registry. And version was stuck at 0.1.0 while the package version was already 0.2.0, so --version reported the wrong release.
The decision logic itself is unchanged: AVAILABLE still requires multi-source confirmation and any uncertainty still yields TAKEN.
Verified end-to-end against the live registries:
google.com -> taken / high (rdap_primary=found)
hfupwefbabfdskf.com -> available / high (rdap_primary=not_found,
whois=not_found)
Both with 0 retries instead of 4 requests each. All 114 tests pass.
Bumps version to 0.2.1.
Fixes #1
Reported-by: @rkahv