{"record":{"id":"369746f563135836","repo":"mvanhorn/last30days-skill","slug":"publish-endpoint-returned-unexpected-json-response","errorCode":null,"errorMessage":"publish endpoint returned unexpected JSON response","messagePattern":"publish endpoint returned unexpected JSON response","errorType":"exception","errorClass":"HtmlPublishError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/html_publish.py","lineNumber":66,"sourceCode":"    )\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.\"\"\"\n    results = HtmlPublishBatchResult()\n    for name, content in documents.items():","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/html_publish.py#L48-L84","documentation":"Raised by publish_html when the 2xx response body parses as JSON but is not an object — e.g. a JSON array or a bare string/number. The contract requires a top-level object because the next step reads result.get('url'); a non-dict would either crash or signal a semantically wrong response, so it is rejected explicitly with HtmlPublishError.","triggerScenarios":"A custom or changed endpoint that returns a JSON array of results or a bare JSON scalar with 200 status; a mock/test server returning json.dumps([\"ok\"]); an API version bump that wraps the payload differently.","commonSituations":"Developing against a stub endpoint during integration; the provider ships a v2 that returns a list for batch operations while the client still expects the v1 object shape.","solutions":["Confirm you are hitting the documented endpoint version (default /v1/sites) that returns an object with a 'url' field.","If using a custom endpoint/mock, return {\"url\": \"https://...\"} as the top-level object.","Print the parsed type (json.loads(body) -> type) from a manual curl to pin down the mismatch."],"exampleFix":"# mock before\nhandle = lambda req: json.dumps([{\"url\": \"https://x\"}]).encode()\n\n# mock after\nhandle = lambda req: json.dumps({\"url\": \"https://x\"}).encode()","handlingStrategy":"try-catch","validationCode":"import json\n\ndef response_is_object(body: str) -> bool:\n    try:\n        return isinstance(json.loads(body), dict)\n    except json.JSONDecodeError:\n        return False","typeGuard":"def is_publish_response(payload: object) -> bool:\n    return isinstance(payload, dict) and isinstance(payload.get(\"url\"), str)","tryCatchPattern":"try:\n    result = publish_html(html)\nexcept HtmlPublishError as e:\n    if 'unexpected JSON' in str(e):\n        # endpoint returned an array/scalar — contract drift; check endpoint version\n        ...","preventionTips":["Pin custom endpoints to the API version whose response is a JSON object with 'url'.","In mocks, always return a dict shaped like the provider: {\"url\": \"https://...\"}.","After provider updates, curl the endpoint once and assert isinstance(json, dict) before shipping."],"tags":["json","html-publish","contract","endpoint"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}