mvanhorn/last30days-skill · error · HtmlPublishError

{exc}

Error message

{exc}

What it means

Raised by publish_html when the request fails with a bare OSError that is neither HTTPError nor URLError — message is str(exc). In urllib terms this catches lower-level socket/timeout problems such as socket.timeout during read, ConnectionResetError, or SSL read failures that do not surface as URLError.

Source

Thrown at skills/last30days/scripts/lib/html_publish.py:59

        payload["password"] = password

    request = Request(
        endpoint,
        data=json.dumps(payload).encode("utf-8"),
        headers={"Content-Type": "application/json", "Accept": "application/json"},
        method="POST",
    )
    open_fn = opener or urlopen
    try:
        with open_fn(request, timeout=timeout) as response:
            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,

View on GitHub (pinned to c7460f6114)

Solutions

  1. Retry once — resets and read timeouts are frequently transient.
  2. Pass a larger timeout= to publish_html for large documents (e.g. timeout=120).
  3. Reduce payload size (minify HTML) if timeouts are consistent.
  4. Inspect e.__cause__ to distinguish socket.timeout / ConnectionResetError and log accordingly.

Example fix

# before
result = publish_html(big_html)  # default timeout=30

# after
result = publish_html(big_html, timeout=120)
Defensive patterns

Strategy: retry

Try / catch

import socket

try:
    result = publish_html(html)
except HtmlPublishError as e:
    cause = e.__cause__
    if isinstance(cause, (socket.timeout, ConnectionResetError)):
        result = publish_html(html, timeout=120)  # one retry with a longer timeout
    else:
        raise

Prevention

When it happens

Trigger: The server accepts the connection then stalls past timeout=30 (default), producing a socket timeout; the connection is reset mid-upload of a large HTML body; SSL handshake data read fails.

Common situations: Publishing very large documents over slow links; flaky Wi-Fi; an endpoint that hangs on oversized payloads; default 30s timeout too short for big uploads on high-latency networks.

Related errors


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