{"record":{"id":"df4ef49d3cc70eed","repo":"mvanhorn/last30days-skill","slug":"publish-endpoint-returned-non-json-response","errorCode":null,"errorMessage":"publish endpoint returned non-JSON response","messagePattern":"publish endpoint returned non-JSON response","errorType":"exception","errorClass":"HtmlPublishError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/html_publish.py","lineNumber":64,"sourceCode":"        headers={\"Content-Type\": \"application/json\", \"Accept\": \"application/json\"},\n        method=\"POST\",\n    )\n    open_fn = opener or urlopen\n    try:\n        with open_fn(request, timeout=timeout) as response:\n            body = response.read().decode(\"utf-8\")\n    except HTTPError as exc:\n        detail = exc.read().decode(\"utf-8\", errors=\"replace\")\n        raise HtmlPublishError(_error_message(exc.code, detail)) from exc\n    except URLError as exc:\n        raise HtmlPublishError(str(exc.reason)) from exc\n    except OSError as exc:\n        raise HtmlPublishError(str(exc)) from exc\n\n    try:\n        result = json.loads(body)\n    except json.JSONDecodeError as exc:\n        raise HtmlPublishError(\"publish endpoint returned non-JSON response\") from exc\n    if not isinstance(result, dict):\n        raise HtmlPublishError(\"publish endpoint returned unexpected JSON response\")\n\n    url = result.get(\"url\")\n    if not isinstance(url, str) or not url.startswith(\"https://\"):\n        raise HtmlPublishError(\"publish endpoint response did not include a valid url\")\n    return result\n\n\ndef publish_html_documents(\n    documents: Mapping[str, str],\n    *,\n    password: str | None = None,\n    endpoint: str = DEFAULT_ENDPOINT,\n    opener: Callable[..., Any] | None = None,\n    timeout: int = 30,\n) -> HtmlPublishBatchResult:\n    \"\"\"Publish a named set of documents, preserving caller order in results.\"\"\"","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/html_publish.py#L46-L82","documentation":"Raised by publish_html when the HTTP request succeeds (2xx) but the response body is not valid JSON. The client sends Accept: application/json and expects a JSON object back; json.JSONDecodeError is caught and re-raised as HtmlPublishError with this fixed message, chained from the original decode error.","triggerScenarios":"endpoint= pointing at a page that returns HTML (a typo hitting the site root, a 200 status error page), a plain-text response from a misconfigured reverse proxy, or a provider change that starts returning non-JSON on success.","commonSituations":"Custom endpoint URL missing the API path (e.g. https://api.ht-ml.app instead of /v1/sites) so the server returns the landing page with 200; an interception proxy injecting an HTML banner; the service replaced by something else at the same URL.","solutions":["Verify the endpoint URL includes the full API path (default https://api.ht-ml.app/v1/sites).","Inspect e.__cause__ (a json.JSONDecodeError) — its lineno/colno plus a manual curl of the endpoint reveals what body came back.","If a proxy rewrites responses, bypass it or fix its configuration."],"exampleFix":"# before\npublish_html(html, endpoint=\"https://api.ht-ml.app\")\n\n# after\npublish_html(html, endpoint=\"https://api.ht-ml.app/v1/sites\")","handlingStrategy":"try-catch","validationCode":"import json, urllib.request\n\ndef endpoint_returns_json(endpoint: str) -> bool:\n    with urllib.request.urlopen(endpoint) as r:  # GET probe only; POST is the real call\n        try:\n            json.loads(r.read().decode(\"utf-8\"))\n            return True\n        except json.JSONDecodeError:\n            return False","typeGuard":null,"tryCatchPattern":"import json\n\ntry:\n    publish_html(html, endpoint=endpoint)\nexcept HtmlPublishError as e:\n    if isinstance(e.__cause__, json.JSONDecodeError):\n        # endpoint returned non-JSON with 200 — verify the URL path, likely hitting an HTML page\n        ...","preventionTips":["Always use the full API path in endpoint= (default .../v1/sites), never the bare host.","Inspect e.__cause__ (JSONDecodeError) to see where the body fails to parse.","In tests, stub the endpoint with a JSON-returning handler, not an HTML page."],"tags":["network","json","html-publish","endpoint"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}