Add custom headers from the env var LLM_EXTRA_HEADERS#476
Conversation
Greptile SummaryThis PR adds support for passing custom HTTP headers to every LiteLLM request via a new
Confidence Score: 3/5Not safe to merge as-is — supplying a syntactically valid but non-object JSON value (e.g. a number or boolean) triggers an unhandled exception at startup instead of the designed error panel. Both
Important Files Changed
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
strix/interface/main.py:92
The `TypeError` raised when `parsed` is not a dict (line 91) is not caught by `except (json.JSONDecodeError, ValueError)`, because `TypeError` is not a subclass of `ValueError`. When `LLM_EXTRA_HEADERS` is set to a valid JSON non-object (e.g. `42`, `true`, `"string"`), the `TypeError` escapes the try/except entirely, bypasses the nice error panel, and surfaces as an unhandled exception traceback instead of calling `sys.exit(1)` with a user-friendly message.
```suggestion
except (json.JSONDecodeError, TypeError, ValueError) as e:
```
### Issue 2 of 3
strix/interface/main.py:99-102
The example shell command is missing single quotes around the JSON value. Without them, the shell will interpret the spaces inside `{}` as argument separators, so copy-pasting this command will produce an incorrect assignment (the variable will only receive `{x-my-header:` and the rest will be treated as separate shell words).
```suggestion
error_text.append(
"export LLM_EXTRA_HEADERS='{\"x-my-header\": \"value\"}'\n",
style="dim white",
)
```
### Issue 3 of 3
strix/config/config.py:233-238
When `parsed` is valid JSON but not a `dict`, the `TypeError` is raised inside the `try` block but is not caught by `except json.JSONDecodeError`. It propagates to every call site (`warm_up_llm`, `check_duplicate`, `_summarize_messages`, `LLMConfig.__init__`) as an unhandled exception. Raising `ValueError` instead (consistent with how `json.JSONDecodeError` is already handled) makes the contract uniform and lets callers handle one exception type.
```suggestion
if isinstance(parsed, dict):
extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None}
else:
raise ValueError("LLM_EXTRA_HEADERS must be a JSON object, got a non-dict value")
except json.JSONDecodeError as e:
raise ValueError(f"Invalid LLM_EXTRA_HEADERS JSON: {e}") from e
```
Reviews (1): Last reviewed commit: "Add custom headers from the env var LLM_..." | Re-trigger Greptile |
| parsed = json.loads(raw_headers) | ||
| if not isinstance(parsed, dict): | ||
| raise TypeError("LLM_EXTRA_HEADERS must be a JSON object, got a non-dict value") | ||
| except (json.JSONDecodeError, ValueError) as e: |
There was a problem hiding this comment.
The
TypeError raised when parsed is not a dict (line 91) is not caught by except (json.JSONDecodeError, ValueError), because TypeError is not a subclass of ValueError. When LLM_EXTRA_HEADERS is set to a valid JSON non-object (e.g. 42, true, "string"), the TypeError escapes the try/except entirely, bypasses the nice error panel, and surfaces as an unhandled exception traceback instead of calling sys.exit(1) with a user-friendly message.
| except (json.JSONDecodeError, ValueError) as e: | |
| except (json.JSONDecodeError, TypeError, ValueError) as e: |
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/interface/main.py
Line: 92
Comment:
The `TypeError` raised when `parsed` is not a dict (line 91) is not caught by `except (json.JSONDecodeError, ValueError)`, because `TypeError` is not a subclass of `ValueError`. When `LLM_EXTRA_HEADERS` is set to a valid JSON non-object (e.g. `42`, `true`, `"string"`), the `TypeError` escapes the try/except entirely, bypasses the nice error panel, and surfaces as an unhandled exception traceback instead of calling `sys.exit(1)` with a user-friendly message.
```suggestion
except (json.JSONDecodeError, TypeError, ValueError) as e:
```
How can I resolve this? If you propose a fix, please make it concise.| error_text.append( | ||
| 'export LLM_EXTRA_HEADERS={"x-my-header": "value"}\n', | ||
| style="dim white", | ||
| ) |
There was a problem hiding this comment.
The example shell command is missing single quotes around the JSON value. Without them, the shell will interpret the spaces inside
{} as argument separators, so copy-pasting this command will produce an incorrect assignment (the variable will only receive {x-my-header: and the rest will be treated as separate shell words).
| error_text.append( | |
| 'export LLM_EXTRA_HEADERS={"x-my-header": "value"}\n', | |
| style="dim white", | |
| ) | |
| error_text.append( | |
| "export LLM_EXTRA_HEADERS='{\"x-my-header\": \"value\"}'\n", | |
| style="dim white", | |
| ) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/interface/main.py
Line: 99-102
Comment:
The example shell command is missing single quotes around the JSON value. Without them, the shell will interpret the spaces inside `{}` as argument separators, so copy-pasting this command will produce an incorrect assignment (the variable will only receive `{x-my-header:` and the rest will be treated as separate shell words).
```suggestion
error_text.append(
"export LLM_EXTRA_HEADERS='{\"x-my-header\": \"value\"}'\n",
style="dim white",
)
```
How can I resolve this? If you propose a fix, please make it concise.| if isinstance(parsed, dict): | ||
| extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None} | ||
| else: | ||
| raise TypeError("LLM_EXTRA_HEADERS must be a JSON object") | ||
| except json.JSONDecodeError as e: | ||
| raise ValueError(f"Invalid LLM_EXTRA_HEADERS JSON: {e}") from e |
There was a problem hiding this comment.
When
parsed is valid JSON but not a dict, the TypeError is raised inside the try block but is not caught by except json.JSONDecodeError. It propagates to every call site (warm_up_llm, check_duplicate, _summarize_messages, LLMConfig.__init__) as an unhandled exception. Raising ValueError instead (consistent with how json.JSONDecodeError is already handled) makes the contract uniform and lets callers handle one exception type.
| if isinstance(parsed, dict): | |
| extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None} | |
| else: | |
| raise TypeError("LLM_EXTRA_HEADERS must be a JSON object") | |
| except json.JSONDecodeError as e: | |
| raise ValueError(f"Invalid LLM_EXTRA_HEADERS JSON: {e}") from e | |
| if isinstance(parsed, dict): | |
| extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None} | |
| else: | |
| raise ValueError("LLM_EXTRA_HEADERS must be a JSON object, got a non-dict value") | |
| except json.JSONDecodeError as e: | |
| raise ValueError(f"Invalid LLM_EXTRA_HEADERS JSON: {e}") from e |
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/config/config.py
Line: 233-238
Comment:
When `parsed` is valid JSON but not a `dict`, the `TypeError` is raised inside the `try` block but is not caught by `except json.JSONDecodeError`. It propagates to every call site (`warm_up_llm`, `check_duplicate`, `_summarize_messages`, `LLMConfig.__init__`) as an unhandled exception. Raising `ValueError` instead (consistent with how `json.JSONDecodeError` is already handled) makes the contract uniform and lets callers handle one exception type.
```suggestion
if isinstance(parsed, dict):
extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None}
else:
raise ValueError("LLM_EXTRA_HEADERS must be a JSON object, got a non-dict value")
except json.JSONDecodeError as e:
raise ValueError(f"Invalid LLM_EXTRA_HEADERS JSON: {e}") from e
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
This PR adds LLM_EXTRA_HEADERS so Strix can attach user-supplied HTTP headers to LiteLLM requests, which is useful for proxy/gateway setups that require additional credentials or custom routing headers.
Changes:
- Added
LLM_EXTRA_HEADERSto config resolution andLLMConfig. - Forwarded extra headers to warm-up, dedupe, memory compression, and main LLM request paths.
- Added startup validation and documentation for the new environment variable.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
strix/llm/memory_compressor.py |
Passes resolved extra headers into memory summarization requests. |
strix/llm/llm.py |
Includes extra_headers in main completion request arguments. |
strix/llm/dedupe.py |
Forwards extra headers in duplicate-check LLM calls. |
strix/llm/config.py |
Stores resolved extra_headers on LLMConfig. |
strix/interface/main.py |
Validates LLM_EXTRA_HEADERS at startup and uses it during LLM warm-up. |
strix/config/config.py |
Adds the new config key and extends resolve_llm_config() to parse/return headers. |
docs/advanced/configuration.mdx |
Documents the new LLM_EXTRA_HEADERS setting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| parsed = json.loads(raw_headers) | ||
| if not isinstance(parsed, dict): | ||
| raise TypeError("LLM_EXTRA_HEADERS must be a JSON object, got a non-dict value") |
| error_text.append(f"Error: {e}\n", style="white") | ||
| error_text.append("\nExample:\n", style="white") | ||
| error_text.append( | ||
| 'export LLM_EXTRA_HEADERS={"x-my-header": "value"}\n', |
| model = Config.get("strix_llm") | ||
| if not model: | ||
| return None, None, None | ||
| return None, None, None, None |
| if isinstance(parsed, dict): | ||
| extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None} | ||
| else: | ||
| raise TypeError("LLM_EXTRA_HEADERS must be a JSON object") |
| if isinstance(parsed, dict): | ||
| extra_headers = {str(k): str(v) for k, v in parsed.items() if v is not None} | ||
| else: | ||
| raise TypeError("LLM_EXTRA_HEADERS must be a JSON object") | ||
| except json.JSONDecodeError as e: | ||
| raise ValueError(f"Invalid LLM_EXTRA_HEADERS JSON: {e}") from e | ||
|
|
Summary
Add support for passing custom HTTP headers to every LiteLLM request via a new
LLM_EXTRA_HEADERSenvironment variable (JSON object). This enables custom API base / gateway setups that require multiple credentials (e.g. gateway key + model key) or additional enterprise headers.Fixes #466
What changed
LLM_EXTRA_HEADERS(string) added to Strix config (Config.llm_extra_headers).strix/interface/main.pynow validatesLLM_EXTRA_HEADERSat startup and shows a clear error panel with an example when invalid.resolve_llm_config()now returns(model, api_key, api_base, extra_headers)and parsesLLM_EXTRA_HEADERSas JSON (must be an object).extra_headersis forwarded tolitellm.completion()in:warm_up_llm)check_duplicate)_summarize_messages)strix/llm/llm.py) viaLLMConfig.extra_headersdocs/advanced/configuration.mdxdocumentsLLM_EXTRA_HEADERSwith a JSON example.Usage
Set
LLM_EXTRA_HEADERSto a JSON object of headers:Notes
Testing