headroomlabs-ai/headroom · error · ValueError
client_id and client_secret are required
Error message
client_id and client_secret are required
What it means
OAuth2ClientCredentials requires both `client_id` and `client_secret` to be non-empty at construction; missing either raises ValueError. Client-credentials flow authenticates the application itself with this pair, so there is nothing sensible to default.
Source
Thrown at plugins/headroom-oauth2/src/headroom_oauth2/provider.py:60
def __init__(
self,
*,
token_url: str,
client_id: str,
client_secret: str,
scopes=None,
audience: str | None = None,
grant_type: str = "client_credentials",
auth_style: str = "post",
extra_params=None,
skew_seconds: int = 60,
timeout_seconds: float = 30.0,
allow_insecure: bool = False,
):
if not token_url:
raise ValueError("token_url is required")
if not client_id or not client_secret:
raise ValueError("client_id and client_secret are required")
if auth_style not in ("post", "basic"):
raise ValueError("auth_style must be 'post' or 'basic'")
if not allow_insecure and not _https_or_local(token_url):
raise ValueError(
"token_url must be https (loopback http allowed for tests; set "
"allow_insecure=True / HEADROOM_OAUTH2_ALLOW_INSECURE=1 to override)"
)
self.token_url = token_url
self.client_id = client_id
self.client_secret = client_secret
self.scopes = list(scopes or [])
self.audience = audience
self.grant_type = grant_type
self.auth_style = auth_style
self.extra_params = dict(extra_params or {})
self.skew = max(0, int(skew_seconds))
self.timeout = timeout_seconds
self._lock = threading.Lock()View on GitHub (pinned to 322425c43b)
Solutions
- Set both credentials in the environment/config the provider reads from, and verify they are non-empty before constructing (see validation code)
- Check secret injection in the deployment: `kubectl describe pod`, CI masked-variable exposure, or print `bool(os.environ.get(...))` (never the value) in a startup probe
- If OAuth2 shouldn't be active in this environment, unset HEADROOM_OAUTH2_TOKEN_URL so the provider is never built
Example fix
# before
provider = OAuth2ClientCredentials(
token_url=os.environ["HEADROOM_OAUTH2_TOKEN_URL"],
client_id=os.environ.get("HEADROOM_OAUTH2_CLIENT_ID", ""),
client_secret=os.environ.get("HEADROOM_OAUTH2_CLIENT_SECRET", ""),
)
# after
missing = [k for k in ("HEADROOM_OAUTH2_CLIENT_ID", "HEADROOM_OAUTH2_CLIENT_SECRET")
if not os.environ.get(k, "").strip()]
if missing:
raise RuntimeError(f"missing oauth2 config: {missing}")
provider = OAuth2ClientCredentials(
token_url=os.environ["HEADROOM_OAUTH2_TOKEN_URL"],
client_id=os.environ["HEADROOM_OAUTH2_CLIENT_ID"],
client_secret=os.environ["HEADROOM_OAUTH2_CLIENT_SECRET"],
) Defensive patterns
Strategy: validation
Validate before calling
REQUIRED = ("HEADROOM_OAUTH2_TOKEN_URL", "HEADROOM_OAUTH2_CLIENT_ID", "HEADROOM_OAUTH2_CLIENT_SECRET")
def oauth2_env_complete(env) -> None:
missing = [k for k in REQUIRED if not str(env.get(k) or "").strip()]
if missing:
raise RuntimeError(f"oauth2 env incomplete, missing: {missing}")
oauth2_env_complete(os.environ)
provider = provider_from_env() Type guard
def credentials_present(client_id, client_secret) -> bool:
return bool(str(client_id or "").strip()) and bool(str(client_secret or "").strip()) Try / catch
try:
provider = OAuth2ClientCredentials(token_url=url, client_id=cid, client_secret=sec)
except ValueError as e:
if "client_id and client_secret are required" in str(e):
raise RuntimeError("deployment error: oauth2 secrets not injected") from e
raise Prevention
- Startup probe: assert required env vars are non-empty (never print their values)
- Watch for empty-string templating: `${VAR}` unset in shell yields empty, not missing
- Confirm secret injection in k8s/CI before first deploy of the plugin
When it happens
Trigger: Constructing the provider with an empty/None `client_id` or `client_secret` — typically values read from env vars (`HEADROOM_OAUTH2_CLIENT_ID`/`..._CLIENT_SECRET`) that are unset in the current shell, pod, or CI runner while TOKEN_URL is set.
Common situations: Secrets not injected into the deployment (Kubernetes secret missing, CI secret not exposed, .env not loaded); secret name typos; running locally with prod config that expects a vault; empty-string values from templating (`CLIENT_ID=${OAUTH_CLIENT_ID}` when the inner var is unset).
Related errors
- token_url is required
- auth_style must be 'post' or 'basic'
- bedrock_eventstream_parse_failed
- Unknown provider: {self.llm_config.provider}
- HEADROOM_LEARN_CLI={cli_override!r} is not a supported CLI.
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/ccf5f9463c23710d.
Report an issue: GitHub.