neondatabase/neon · error
Failed to get feature flags: {}, {}
Error message
Failed to get feature flags: {}, {} What it means
get_feature_flags_local_evaluation_raw issues a GET with the server_api_key as a bearer token to either /api/feature_flag/local_evaluation (new secure key) or /api/projects/{project_id}/feature_flags/local_evaluation (legacy personal token). If PostHog answers with a non-2xx status, the error embeds both the HTTP status and the response body, which usually states the exact reason (invalid key, unknown project, plan limits).
Source
Thrown at libs/posthog_client_lite/src/lib.rs:577
)
} else {
// The old personal API token
format!(
"{}/api/projects/{}/feature_flags/local_evaluation",
self.config.private_api_url, self.config.project_id
)
};
let response = self
.client
.get(url)
.bearer_auth(&self.config.server_api_key)
.send()
.await?;
let status = response.status();
let body = response.text().await?;
if !status.is_success() {
return Err(anyhow::anyhow!(
"Failed to get feature flags: {}, {}",
status,
body
));
}
Ok(body)
}
/// Fetch the feature flag specs from the server.
///
/// This is unfortunately an undocumented API at:
/// - <https://posthog.com/docs/api/feature-flags#get-api-projects-project_id-feature_flags-local_evaluation>
/// - <https://posthog.com/docs/feature-flags/local-evaluation>
///
/// The handling logic in [`FeatureStore`] mostly follows the Python API implementation.
/// See `_compute_flag_locally` in <https://github.com/PostHog/posthog-python/blob/master/posthog/client.py>
pub async fn get_feature_flags_local_evaluation(
&self,
) -> Result<LocalEvaluationResponse, anyhow::Error> {View on GitHub (pinned to 8f60b04da4)
Solutions
- Read the embedded response body first -- it distinguishes bad key (401), bad project (404), and plan/limit errors
- Verify server_api_key is a PostHog personal/secure API token with read access to the project, and project_id matches it
- Confirm private_api_url is the private/API base URL of your PostHog instance, not the public capture URL
- For 5xx/timeouts, retry with backoff (transient PostHog or network issues); for 4xx fix credentials/configuration instead
Example fix
# before POSTHOG_API_URL='https://eu.i.posthog.com' # public capture URL used as private API POSTHOG_API_KEY='phx_...' # client key used as server key # after POSTHOG_API_URL='https://eu.i.posthog.com' # same host is fine for cloud POSTHOG_SERVER_API_KEY='phx_team_personal_token' # personal/secure token w/ read scope
Defensive patterns
Strategy: retry
Validate before calling
// Cheap pre-flight: distinguish credential errors (do not retry) from transient ones.
async fn fetch_feature_flags_guarded(client: &PosthogClientLite) -> anyhow::Result<String> {
match client.get_feature_flags_local_evaluation_raw().await {
Err(e) => {
let msg = format!("{e:#}");
if msg.contains(" 401, ") || msg.contains(" 403, ") || msg.contains(" 404, ") {
anyhow::bail!("posthog credentials/project misconfigured (fatal): {msg}");
}
Err(e) // 5xx / timeouts: retriable by caller
}
ok => ok,
}
} Try / catch
use backoff::{backoff::Backoff, ExponentialBackoff};
let mut bo = ExponentialBackoff::default(); // default max elapsed ~15m, jittered
loop {
match client.get_feature_flags_local_evaluation_raw().await {
Ok(body) => break Ok(body),
Err(e) => {
let msg = format!("{e:#}");
let fatal = msg.contains(" 401, ") || msg.contains(" 403, ") || msg.contains(" 404, ");
if fatal || bo.next_backoff().is_none() {
break Err(anyhow::anyhow!("feature flag fetch failed: {msg}"));
}
tracing::warn!("retrying posthog feature flags after: {msg}");
}
}
} Prevention
- Separate public (client_api_key) and private (server_api_key) credentials in config from day one
- Run a startup probe: one feature-flag GET, and fail fast on 401/404 with the body logged
- Cache last-known-good flags so transient PostHog failures degrade instead of erroring
- Monitor the embedded status/body pairs; a sudden 401 after working usually means key rotation
When it happens
Trigger: 401/403 with a wrong or expired server_api_key; 404 from a wrong project_id or wrong private_api_url base; 402 or plan-related errors when the PostHog plan lacks local evaluation; 5xx during a PostHog outage -- each surfaces as 'Failed to get feature flags: <status>, <body>'.
Common situations: Rotated personal API keys not updated in config; mixing up client_api_key (public) and server_api_key (private); pointing private_api_url at the public capture endpoint; self-hosted PostHog behind an auth-stripping proxy; egress blocked by firewall producing proxied 4xx/5xx responses.
Related errors
- Failed to capture events: {}, {}
- setting scheduling policy unsuccessful for safekeeper {node_
- Unexpected message {:?} while waiting for handshake
- client did not connect with TLS
- not implemented
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/414ba0adfd8a845d.
Report an issue: GitHub.