headroomlabs-ai/headroom · critical · RuntimeError
headroom-oauth2 misconfigured: {e}
Error message
headroom-oauth2 misconfigured: {e} What it means
The plugin's `install(app, config)` entry point calls `provider_from_env()` and wraps any ValueError from env parsing (e.g. a non-integer numeric setting, a bad auth_style, or a non-https token URL) in a RuntimeError marked fail-closed. The plugin deliberately refuses to half-load: a malformed OAuth2 config must crash proxy startup instead of running without authentication.
Source
Thrown at plugins/headroom-oauth2/src/headroom_oauth2/__init__.py:100
token_url=token_url,
client_id=env.get("HEADROOM_OAUTH2_CLIENT_ID", ""),
client_secret=env.get("HEADROOM_OAUTH2_CLIENT_SECRET", ""),
scopes=_split(env.get("HEADROOM_OAUTH2_SCOPES")),
audience=env.get("HEADROOM_OAUTH2_AUDIENCE") or None,
grant_type=env.get("HEADROOM_OAUTH2_GRANT_TYPE", "client_credentials"),
auth_style=env.get("HEADROOM_OAUTH2_AUTH_STYLE", "post"),
extra_params={"resource": resource} if resource else None,
allow_insecure=allow_insecure,
**kwargs,
)
def install(app: Any, config: Any) -> None:
"""Headroom proxy-extension entry point: install(app, config) -> None."""
try:
provider = provider_from_env()
except ValueError as e:
raise RuntimeError(f"headroom-oauth2 misconfigured: {e}") from None # fail-closed
if provider is None:
log.info("headroom-oauth2 loaded but HEADROOM_OAUTH2_TOKEN_URL unset; no-op")
return
static = parse_headers(os.environ.get("HEADROOM_OAUTH2_HEADERS"))
if static:
try:
# litellm's import runs load_dotenv and can inject .env values into os.environ;
# snapshot and restore so we never leak unrelated keys into the process env.
_before = dict(os.environ)
import litellm
# drop keys litellm/load_dotenv added, restore any it changed (no empty-env window)
for k in list(os.environ):
if k not in _before:
del os.environ[k]
os.environ.update(_before)
litellm.headers = {**(getattr(litellm, "headers", None) or {}), **static}
log.info("headroom-oauth2: static upstream headers: %s", list(static))View on GitHub (pinned to 322425c43b)
Solutions
- Read the `<e>` suffix — it names the actual bad setting (e.g. `KEY=value is not an integer`, `token_url must be https`); fix that variable per its message
- Dry-run the config before startup: `python -c "from headroom_oauth2 import provider_from_env; provider_from_env()"` to validate env in isolation
- If OAuth2 is not intended in this deployment, unset HEADROOM_OAUTH2_TOKEN_URL — with no token URL the plugin logs 'no-op' and loads cleanly
Example fix
# before $ HEADROOM_OAUTH2_TOKEN_URL=https://idp/oauth2/token HEADROOM_OAUTH2_TIMEOUT_SECONDS=30s headroom-proxy RuntimeError: headroom-oauth2 misconfigured: HEADROOM_OAUTH2_TIMEOUT_SECONDS='30s' is not an integer # after $ HEADROOM_OAUTH2_TOKEN_URL=https://idp/oauth2/token HEADROOM_OAUTH2_TIMEOUT_SECONDS=30 headroom-proxy
Defensive patterns
Strategy: validation
Validate before calling
from headroom_oauth2 import provider_from_env
try:
provider_from_env() # dry-run: validates all HEADROOM_OAUTH2_* settings
except ValueError as e:
raise SystemExit(f"oauth2 config invalid: {e}") from e
# only then start the proxy / install the plugin Type guard
def oauth2_env_valid() -> bool:
try:
provider_from_env()
return True
except ValueError:
return False Try / catch
try:
install(app, config)
except RuntimeError as e:
if "headroom-oauth2 misconfigured" in str(e):
raise SystemExit(f"fix HEADROOM_OAUTH2_* env: {e}") from e # fail closed, do not boot
raise Prevention
- Validate env before boot; never catch-and-continue this RuntimeError — it is fail-closed by design
- Keep a single source of truth for oauth2 env (one .env or secret) and lint it in CI
- Unset HEADROOM_OAUTH2_TOKEN_URL in environments where OAuth2 is intentionally off
When it happens
Trigger: Enabling the plugin (HEADROOM_OAUTH2_TOKEN_URL set) with any malformed sibling variable: non-integer `HEADROOM_OAUTH2_TIMEOUT_SECONDS`, `HEADROOM_OAUTH2_AUTH_STYLE=digest`, or an http token URL without the insecure override — startup then aborts with `RuntimeError: headroom-oauth2 misconfigured: <cause>`.
Common situations: Deploying the proxy with templated env files where one variable got mangled; enabling OAuth2 for the first time and missing a required setting; CI secrets injected with wrong types (quoted numbers); the underlying cause message points at the specific bad variable.
Related errors
- {key}={raw!r} is not an integer
- recommendations IO error at {path}: {source}
- bedrock_eventstream_parse_failed
- bedrock_eventstream_crc_mismatch
- Unknown agent: {name!r}. Available: {available}
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/8e6436d7c51a95a3.
Report an issue: GitHub.