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

  1. 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
  2. Dry-run the config before startup: `python -c "from headroom_oauth2 import provider_from_env; provider_from_env()"` to validate env in isolation
  3. 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

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


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/8e6436d7c51a95a3. Report an issue: GitHub.