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

  1. Inspect the raw response (curl the endpoint with the same payload) and compare against the expected {"url": "https://..."} shape.
  2. For self-hosted/custom endpoints, make the response emit the absolute HTTPS URL.
  3. 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

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


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/ccce9925d3229128. Report an issue: GitHub.