crowdsecurity/crowdsec · error

unexpected status code: %d

Error message

unexpected status code: %d

What it means

The longpollclient's poll() performs a streaming HTTP request to the LAPI (or other endpoint) and expects a 200 response before decoding the JSON stream. If the response carries any other status code, it returns 'unexpected status code: %d'. 401 responses are special-cased into errUnauthorized earlier in the function, so any other non-200 (403, 404, 500, 502…) surfaces through this error, aborting the poll loop.

Source

Thrown at pkg/longpollclient/client.go:106

		return err
	}

	defer resp.Body.Close()

	requestId := resp.Header.Get("X-Amzn-Trace-Id")
	logger = logger.WithField("request-id", requestId)
	if resp.StatusCode != http.StatusOK {
		c.logger.Errorf("unexpected status code: %d", resp.StatusCode)
		if resp.StatusCode == http.StatusPaymentRequired {
			bodyContent, err := io.ReadAll(resp.Body)
			if err != nil {
				logger.Errorf("failed to read response body: %s", err)
				return err
			}
			logger.Error(string(bodyContent))
			return errUnauthorized
		}
		return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	decoder := json.NewDecoder(resp.Body)

	for {
		select {
		case <-c.t.Dying():
			logger.Debugf("dying")
			close(c.c)
			return nil
		case <-ctx.Done():
			logger.Debugf("context canceled")
			close(c.c)
			return ctx.Err()
		default:
			var pollResp pollResponse
			err = decoder.Decode(&pollResp)
			if err != nil {

View on GitHub (pinned to 909b515798)

Solutions

  1. Check the reported status code: 404 → wrong api_url/path, 403 → permissions, 5xx → LAPI or proxy problem
  2. Verify api_url, port, and route in the client's configuration (cscli bouncers / LAPI address)
  3. Check LAPI and any reverse-proxy logs around the failure time
  4. Confirm the client's credentials exist and are valid (cscli bouncers list / validate), then retry

Example fix

// before (config)
api_url: http://127.0.0.1:8081/api/v1/wrong-route
// after
api_url: http://127.0.0.1:8080/
Defensive patterns

Strategy: retry

Validate before calling

# Before starting the poller, probe the endpoint:
curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $KEY" "$API_URL/watch/login/decision"

Type guard

// Treat only 200 as success; handle known codes explicitly:
switch resp.StatusCode {
case http.StatusOK: /* proceed */
case http.StatusUnauthorized: /* re-auth */
default: return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

Try / catch

if err := poller.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "unexpected status code") {
        // log the code, check LAPI health, backoff and retry
        time.Sleep(retryBackoff)
    }
}

Prevention

When it happens

Trigger: pollEvents → poll() receives resp.StatusCode != 200 and != 401: LAPI not reachable behind a reverse proxy returning 502/503, wrong URL/port in config (404), bouncer/user credentials valid enough to pass preflight but lacking rights (403), or LAPI crashed mid-request (500).

Common situations: Long-polling bouncers (appsec/notification setups) pointing at a wrong api_url; TLS proxy returning HTML error pages with 502; LAPI restarted during polling; version mismatch producing 404 on the poll route.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/999c36eae12fd15b. Report an issue: GitHub.