{"record":{"id":"296c83def832a55a","repo":"PrefectHQ/fastmcp","slug":"horizon-returned-an-invalid-organization-cursor","errorCode":null,"errorMessage":"Horizon returned an invalid organization cursor","messagePattern":"Horizon returned an invalid organization cursor","errorType":"exception","errorClass":"HorizonResponseError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/cli/deploy/horizon_client.py","lineNumber":320,"sourceCode":"        while True:\n            params = {\"limit\": 100}\n            if cursor is not None:\n                params[\"cursor\"] = cursor\n            response = await self._request(\n                \"GET\",\n                \"/api/v0/me/organizations\",\n                authenticated=True,\n                params=params,\n            )\n            self._require_status(response, 200)\n            result = self._validate_response(response, _OrganizationsResponse)\n            organizations.extend(result.items)\n\n            cursor = result.meta.nextCursor\n            if cursor is None:\n                return tuple(organizations)\n            if cursor in seen_cursors:\n                raise HorizonResponseError(\n                    \"Horizon returned an invalid organization cursor\",\n                    status_code=response.status_code,\n                )\n            seen_cursors.add(cursor)\n\n    async def revoke_current_api_key(self) -> None:\n        response = await self._request(\n            \"DELETE\",\n            \"/api/v0/me/api-key\",\n            authenticated=True,\n        )\n        self._require_status(response, 204)\n","sourceCodeStart":302,"sourceCodeEnd":333,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/cli/deploy/horizon_client.py#L302-L333","documentation":"HorizonResponseError raised when the paginated organization listing from the Horizon API returns a nextCursor that was already seen. This indicates the server's cursor pagination is looping instead of advancing, so continuing would iterate forever. The library guards against this infinite-loop condition and aborts with the HTTP status code of the failing response.","triggerScenarios":"Calling list_organizations() when the Horizon server returns a pagination chain whose meta.nextCursor repeats a previously returned cursor (a cycle), instead of eventually returning None.","commonSituations":"A Horizon backend bug or upgrade regression in cursor generation; a broken/misconfigured Horizon deployment replaying the same page; proxy caching returning a stale page for each cursor; a corrupted or duplicated cursor value in server metadata.","solutions":["Retry the call after a short delay — a transient Horizon fault may have produced the looping page","Verify the Horizon server version is current; report the looping-cursor bug to the server operator if it persists","Check for proxies/caches between the client and Horizon that could replay a cached page","Capture the status_code and cursors observed and file an issue with fastmcp"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from fastmcp.cli.deploy.horizon_client import HorizonResponseError\n\nfor attempt in range(3):\n    try:\n        orgs = await client.list_organizations()\n        break\n    except HorizonResponseError as exc:\n        if attempt == 2:\n            logger.error(\"Horizon pagination looped (status %s)\", exc.status_code)\n            raise\n        await asyncio.sleep(2 ** attempt)","preventionTips":["Keep the Horizon server and fastmcp client versions in sync","Avoid caching proxies between the client and Horizon for authenticated API routes","Log the observed cursor chain to make server-side pagination bugs reproducible"],"tags":["pagination","http","api"],"backgroundTag":"pagination-cursor-loop","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}