mvanhorn/last30days-skill · error · HtmlPublishError
publish endpoint response did not include a valid url
Error message
publish endpoint response did not include a valid url
What it means
Raised by publish_html when the response is a JSON object but its 'url' field is missing, is not a string, or does not start with 'https://'. This is the final validation of the publish contract: the whole point of the call is to obtain a hosted HTTPS URL, so a response without one is treated as a failure even if the status was 200.
Source
Thrown at skills/last30days/scripts/lib/html_publish.py:70
body = response.read().decode("utf-8")
except HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise HtmlPublishError(_error_message(exc.code, detail)) from exc
except URLError as exc:
raise HtmlPublishError(str(exc.reason)) from exc
except OSError as exc:
raise HtmlPublishError(str(exc)) from exc
try:
result = json.loads(body)
except json.JSONDecodeError as exc:
raise HtmlPublishError("publish endpoint returned non-JSON response") from exc
if not isinstance(result, dict):
raise HtmlPublishError("publish endpoint returned unexpected JSON response")
url = result.get("url")
if not isinstance(url, str) or not url.startswith("https://"):
raise HtmlPublishError("publish endpoint response did not include a valid url")
return result
def publish_html_documents(
documents: Mapping[str, str],
*,
password: str | None = None,
endpoint: str = DEFAULT_ENDPOINT,
opener: Callable[..., Any] | None = None,
timeout: int = 30,
) -> HtmlPublishBatchResult:
"""Publish a named set of documents, preserving caller order in results."""
results = HtmlPublishBatchResult()
for name, content in documents.items():
try:
results[name] = publish_html(
content,
password=password,View on GitHub (pinned to c7460f6114)
Solutions
- Inspect the raw response (curl the endpoint with the same payload) and compare against the expected {"url": "https://..."} shape.
- For self-hosted/custom endpoints, make the response emit the absolute HTTPS URL.
- For provider-side drift, pin/roll back to the endpoint version that returns the documented shape.
Example fix
# custom endpoint before
return {"url": "/sites/abc"}
# custom endpoint after
return {"url": "https://your-host/sites/abc"} Defensive patterns
Strategy: try-catch
Validate before calling
def has_valid_url(payload: dict) -> bool:
url = payload.get("url")
return isinstance(url, str) and url.startswith("https://") Type guard
def is_valid_publish_url(url: object) -> bool:
return isinstance(url, str) and url.startswith("https://") Try / catch
try:
result = publish_html(html)
except HtmlPublishError as e:
if 'valid url' in str(e):
# 200 but no usable url — inspect the raw response; likely endpoint/version mismatch
... Prevention
- Custom endpoints must return an absolute https:// URL, not a relative path.
- When the provider changes its response shape, compare raw curl output against the client's expectation.
- Treat a 200-without-url as a contract failure worth alerting on, not a transient error.
When it happens
Trigger: Provider returns {"status": "ok"} without a url; url is null or a number; url uses http:// instead of https://; a partially deployed provider version returns the old response shape; a mock omitting the field.
Common situations: Endpoint contract drift after a provider update; custom self-hosted endpoint that returns a relative path like /sites/abc instead of an absolute HTTPS URL; a stub used in tests that forgets the field.
Related errors
- publish endpoint returned unexpected JSON response
- publish endpoint returned non-JSON response
- {label.capitalize()} file {file_path} must be a top-level JS
- The {label} file is bound to bundle_id {file_bundle_id!r} bu
- Judgments file {path} must carry a top-level \"judgments\" l
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/ccce9925d3229128.
Report an issue: GitHub.