{"record":{"id":"bb621cd77d8c8fdb","repo":"mvanhorn/last30days-skill","slug":"stocktwits-symbol-stream-was-not-returned","errorCode":null,"errorMessage":"StockTwits symbol stream was not returned","messagePattern":"StockTwits symbol stream was not returned","errorType":"exception","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/stocktwits.py","lineNumber":323,"sourceCode":"\n    if datum_key != \"pct_bullish\":\n        raise KeyError(f\"Unsupported StockTwits datum: {datum_key}\")\n    symbol = str(item.metadata.get(\"symbol\") or item.container or \"\").strip().upper()\n    if not symbol:\n        raise ValueError(\"StockTwits item has no symbol\")\n    url = _STREAM_URL.format(symbol=urllib.parse.quote(symbol))\n    window = item.metadata.get(\"freshness_window\") or {}\n    depth = str(window.get(\"depth\") or \"default\")\n    target = _DEPTH.get(depth, _DEPTH[\"default\"])\n    messages: list[dict[str, Any]] = []\n    cursor_max = None\n    while len(messages) < target:\n        request_kwargs: dict[str, Any] = {\"timeout\": 10, \"retries\": 2}\n        if cursor_max:\n            request_kwargs[\"params\"] = {\"max\": cursor_max}\n        data = http.request(\"GET\", url, **request_kwargs)\n        if not isinstance(data, dict) or not isinstance(data.get(\"messages\"), list):\n            raise KeyError(\"StockTwits symbol stream was not returned\")\n        batch = data[\"messages\"]\n        if not batch:\n            break\n        messages.extend(batch)\n        cursor = data.get(\"cursor\") or {}\n        if not cursor.get(\"more\") or not cursor.get(\"max\"):\n            break\n        cursor_max = cursor[\"max\"]\n    messages = _filter_by_date(\n        messages,\n        window.get(\"from_date\"),\n        window.get(\"to_date\"),\n    )\n    aggregate = aggregate_sentiment(messages)\n    value = aggregate.get(\"pct_bullish\")\n    if value is None:\n        raise KeyError(\"StockTwits stream has no tagged sentiment\")\n    newest = max(","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/stocktwits.py#L305-L341","documentation":"KeyError raised inside StockTwits refetch_datum when a paginated GET to the symbol stream returns a payload that is not a dict or has no 'messages' list. The StockTwits public API normally always includes messages (possibly empty), so this signals a malformed/proxy/error response masquerading as data.","triggerScenarios":"http.request('GET', stream_url) returning an HTML error page parsed as non-dict, a JSON error envelope without 'messages', or None/empty body - any of these fail the isinstance check and raise immediately inside the pagination loop.","commonSituations":"StockTwits API outages or Cloudflare challenge pages; rate limiting returning a JSON error body with 200; corporate proxies injecting block pages; API contract changes dropping the messages field.","solutions":["Catch KeyError and treat the datum as unrefreshable, keeping the cached value with a stale flag","Retry once after a short delay - transient gateway errors are common on the unauth endpoint","Log the raw payload shape when this fires to distinguish outage from contract change"],"exampleFix":"# before\ndata = refetch_datum(item, \"pct_bullish\")  # KeyError on bad envelope\n\n# after\ntry:\n    data = refetch_datum(item, \"pct_bullish\")\nexcept KeyError:\n    data = {\"value\": cached_value, \"stale\": True}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    datum = refetch_datum(item, \"pct_bullish\")\nexcept KeyError as e:\n    if \"stream was not returned\" in str(e):\n        datum = retry_once_with_delay(item, delay=5) or cached_with_stale_flag(item)","preventionTips":["Retry once after a short delay - Cloudflare/gateway hiccups are common","Log the raw payload when this fires to catch API contract changes early","Keep last-known values with a stale flag instead of dropping the datum"],"tags":["stocktwits","api","keyerror","malformed-response"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}