Skip to content

fix: HAProxy routing, env vars, + canonical endpoint verification#259

Closed
sheepdestroyer wants to merge 10 commits into
masterfrom
test/canonical-endpoint-verification
Closed

fix: HAProxy routing, env vars, + canonical endpoint verification#259
sheepdestroyer wants to merge 10 commits into
masterfrom
test/canonical-endpoint-verification

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes HAProxy routing, container env vars, and adds comprehensive endpoint verification.

HAProxy fixes (5 issues)

1. Dev subpath routing hit prod backends
Prod subpath rules matched before the host_dev rule, so dev.vendeuvre.lan/llm-routing/litellm/ui was served by prod LiteLLM (port 4000) instead of dev (4010). Fixed by moving dev rules to position 0 — they now match first.

2. Missing dev-specific backends
Added dev-litellm-backend (port 4010), dev-langfuse-backend (port 3011), and dev-llm-routing-backend (port 5010) with the same path-rewrite logic as their prod counterparts.

3. Langfuse empty-path 400 on HTTP/1.1
The regsub(^/llm-routing/langfuse,,) produced an empty string when the path was exactly /llm-routing/langfuse. HAProxy then sent GET HTTP/1.1 (empty path) which is invalid HTTP → 400. Fixed by replacing with /: regsub(^/llm-routing/langfuse,/,).

4. Langfuse /_next assets 404
Moved root-relative SPA asset rules before the host_dashboard catch-all so Langfuse static assets route correctly.

5. LiteLLM /ui/login 404
Added use_backend litellm-backend if url_litellm_ui before the catch-all so LiteLLM SPA routes work.

Container env fixes

6. LiteLLM UI password not recognized
.env had UI_USERNAME/UI_PASSWORD but LiteLLM expects LITELLM_UI_USERNAME/LITELLM_UI_PASSWORD — added to pod.yaml + start-stack.sh rendering.

7. Langfuse post-login redirect to localhost
NEXTAUTH_URL was hardcoded to http://localhost:LANGFUSE_WEB_PORT — now derived from PUBLIC_BASE_URL.

8. Prod .env missing PUBLIC_BASE_URL
Added PUBLIC_BASE_URL="https://x570.vendeuvre.lan/llm-routing" to prod .env so the verification script can test canonical HTTPS URLs.

Verification script (scripts/verification/verify_canonical_endpoints.py)

Comprehensive endpoint test suite:

Section Endpoints
Router API /v1/models, /metrics, /dashboard, /api/dashboard-stats, /visualizer
LiteLLM /health/liveness, /health/readiness, /v1/models, /llm-routing/litellm/ui/
Langfuse /api/public/health, / (web UI)
Infrastructure MinIO /minio/health/live, ClickHouse /ping
E2E chat (router) 3 completions through triage router
LiteLLM direct 1 completion directly to LiteLLM
Canonical URLs 6 GET + 1 POST through public HTTPS (graceful DNS skip)

Review fixes applied (from PR #258)

  • Sourcery: Extracted parse_chat_response() helper to deduplicate chat completion parsing across all three call sites (E2E, LiteLLM direct, canonical POST)
  • Sourcery: Infra health checks now read MinIO and ClickHouse ports from .env (MINIO_S3_PORT, CLICKHOUSE_HTTP_PORT) instead of hardcoding 9002/8123
  • Sourcery: .env parser now logs which files were loaded and warns on missing files
  • Gemini Code Assist: Full isinstance guards on data, choices, first_choice, and message in the shared parse_chat_response() helper
  • Gemini Code Assist: .env parser handles export prefix and strips inline comments
  • Global consistency: Applied defensive chat response parsing to all three classifier scripts (benchmark_classifier.py, retry_errors.py, reclassify_all.py)

Test results

  • 92 unit tests: all passing
  • Prod verification: 24/24 passed
  • Dev verification: 24/24 passed

Summary by Sourcery

Add a canonical endpoint verification script and wire it into documentation, while tightening chat response parsing and container env configuration for LiteLLM and Langfuse.

New Features:

  • Introduce scripts/verification/verify_canonical_endpoints.py to validate router, LiteLLM, Langfuse, infrastructure, and public HTTPS endpoints across prod and dev.

Enhancements:

  • Document canonical endpoint verification usage and coverage in README.
  • Harden classifier scripts’ chat response parsing to defensively handle malformed or unexpected API responses.
  • Derive Langfuse NEXTAUTH_URL from PUBLIC_BASE_URL for correct OAuth redirects.
  • Expose LiteLLM UI credentials into pod.yaml via new env placeholders to align with LiteLLM’s expected variable names.

Documentation:

  • Add README section describing canonical endpoint verification script, covered endpoints, and PUBLIC_BASE_URL requirements.

Tests:

  • Add an automated verification suite that exercises critical router, LiteLLM, Langfuse, infrastructure, and canonical HTTPS endpoints for both prod and dev environments.

Chores:

  • Improve .env loading in the verification script, including support for overlays, exports, inline comments, and env-driven ports for MinIO and ClickHouse.

boy added 9 commits July 11, 2026 22:47
Reads ports/URLs from .env (and .env.dev overlay), validates:
- Router: /v1/models, /metrics, /dashboard, /api/dashboard-stats
- LiteLLM: /health/liveness, /health/readiness, /v1/models
- Langfuse: /api/public/health
- E2E: 3 chat completions through triage router
- Canonical HTTPS URLs (graceful skip on DNS failure)

Usage:
  python scripts/verification/verify_canonical_endpoints.py         # prod
  python scripts/verification/verify_canonical_endpoints.py --dev   # dev
- Pass UI_USERNAME/UI_PASSWORD from .env to LiteLLM container as
  LITELLM_UI_USERNAME/LITELLM_UI_PASSWORD (LiteLLM's expected env vars)
- Derive NEXTAUTH_URL from PUBLIC_BASE_URL instead of hardcoding
  localhost — fixes Langfuse post-login redirect to broken URL
- LiteLLM: /llm-routing/litellm/ui/ (SERVER_ROOT_PATH prefix required)
- Langfuse: / (web UI root)
- Canonical URLs: /litellm/ui/ and /langfuse
…cation

- Router: /visualizer endpoint
- Infrastructure: MinIO /minio/health/live, ClickHouse /ping
- LiteLLM: direct chat completion (bypasses triage router)
- Canonical URLs: /visualizer
- POST agent-simple-core through public URL (dev.vendeuvre.lan)
- Validates full HTTPS path: HAProxy → dev router → LiteLLM → model
- Graceful DNS skip when host unreachable
- All three chat completion parsers now use .get() on choices/message
  instead of direct indexing (Gemini Code Assist review)
- Canonical URL skipped checks are tracked separately and shown
  in summary as 'X skipped' (Sourcery review)
… ports, .env logging

- Extract parse_chat_response() helper to deduplicate chat completion parsing
  across test_e2e_chat, test_litellm_direct_chat, and test_canonical_urls
- Add isinstance guards for defensive JSON response parsing (Gemini suggestion)
- Load MinIO and ClickHouse ports from .env (MINIO_S3_PORT, CLICKHOUSE_HTTP_PORT)
  instead of hardcoding 9002/8123 (Sourcery suggestion)
- Log which .env files were loaded and warn on missing files (Sourcery suggestion)
- Handle export prefix and inline comments in .env parser (Gemini suggestion)
Apply isinstance guards on choices/message across benchmark_classifier.py,
retry_errors.py, and reclassify_all.py — same pattern as the verification
script's parse_chat_response() helper. Prevents unhandled exceptions when
the API returns unexpected response structures.
@sourcery-ai

sourcery-ai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fixes HAProxy routing precedence and dev backends, aligns container env configuration for LiteLLM and Langfuse, and adds a comprehensive canonical endpoint verification script with hardened chat response parsing in classifier utilities.

Sequence diagram for canonical HTTPS chat completion verification

sequenceDiagram
    actor Operator
    participant VerificationScript as verify_canonical_endpoints_py
    participant RouterAPI as Router_v1_chat_completions
    participant AgentModel as agent_simple_core

    Operator ->> VerificationScript: python verify_canonical_endpoints.py
    VerificationScript ->> RouterAPI: POST PUBLIC_BASE_URL/v1/chat/completions
    RouterAPI ->> AgentModel: generate completion
    AgentModel -->> RouterAPI: JSON response
    RouterAPI -->> VerificationScript: JSON response
    VerificationScript ->> parse_chat_response: parse_chat_response(data)
    parse_chat_response -->> VerificationScript: content, reasoning_content
    VerificationScript -->> Operator: report test result
Loading

File-Level Changes

Change Details Files
Adjust HAProxy routing rules to correctly prioritize dev subpaths and fix Langfuse/LiteLLM path handling.
  • Move dev host and subpath ACL/use_backend rules to the top of the HAProxy config so dev traffic never falls through to prod backends.
  • Add dev-specific backends for LiteLLM, Langfuse, and llm-routing with mirrored path-rewrite logic from prod.
  • Fix Langfuse empty-path issue by rewriting /llm-routing/langfuse to / instead of an empty string.
  • Reorder SPA/static asset routing so Langfuse /_next assets are evaluated before dashboard catch-all rules.
  • Add explicit LiteLLM UI routing for /ui and /ui/login before catch-alls to avoid 404s on the SPA.
haproxy.cfg
related HAProxy configuration file(s)
Update pod and stack environment handling so LiteLLM UI and Langfuse NEXTAUTH use correct variables and URLs.
  • Export UI username/password into the pod rendering context and map them to LiteLLM-specific LITELLM_UI_USERNAME/LITELLM_UI_PASSWORD placeholders.
  • Derive NEXTAUTH_URL from PUBLIC_BASE_URL and inject it into the Langfuse container env instead of using a localhost-based URL.
  • Ensure PUBLIC_BASE_URL is present in prod .env so HTTPS canonical URL tests can run, using the https://x570.vendeuvre.lan/llm-routing value.
start-stack.sh
pod.yaml
.env
.env.dev
Add canonical endpoint verification script that reads configuration from .env files and exercises router, LiteLLM, Langfuse, infra, and public HTTPS endpoints for both prod and dev.
  • Implement .env loader that supports overlays (.env plus optional .env.dev), handles export prefixes, strips inline comments, and logs which env files were loaded.
  • Introduce per-section test helpers for router API, LiteLLM health/UI, Langfuse health/UI, MinIO and ClickHouse health, E2E chat via triage router, direct LiteLLM chat, and canonical HTTPS URLs with graceful DNS/unreachable skipping.
  • Add robust parse_chat_response helper to safely extract content and reasoning_content from chat completion responses, used by multiple test paths.
  • Wire CLI flags --dev and --prod to select environment overlay and summarize results with pass/skip counts and non-zero exit on failures.
scripts/verification/verify_canonical_endpoints.py
README.md
Harden chat classifier scripts to defensively parse completion responses and avoid crashes on malformed data.
  • Replace direct indexing of choices[0].message.content in benchmark_classifier.py with isinstance checks on choices, the first choice, and its message before reading content.
  • Apply similar defensive parsing in reclassify_all.py, returning explicit error strings when choices/message shapes are invalid.
  • Update retry_errors.py to only read content when choices is a non-empty list of dicts with a dict message, otherwise leave content empty so tier normalization can still run safely.
scripts/benchmark_classifier.py
scripts/reclassify_all.py
scripts/retry_errors.py
Align README documentation with the new canonical endpoint verification workflow.
  • Add a "Canonical Endpoint Verification" section describing prod/dev invocation of verify_canonical_endpoints.py.
  • Document the endpoint coverage matrix (router API, LiteLLM, Langfuse, infrastructure, E2E chat, LiteLLM direct, canonical HTTPS URLs) and the requirement for PUBLIC_BASE_URL in .env.
  • Clarify that dev .env.dev already has PUBLIC_BASE_URL and prod .env must set it to https://x570.vendeuvre.lan/llm-routing.
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sheepdestroyer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93f6949f-b872-47dd-9762-492c63af04ed

📥 Commits

Reviewing files that changed from the base of the PR and between 6d064bb and 477d5e8.

📒 Files selected for processing (8)
  • README.md
  • pod.yaml
  • scripts/benchmark_classifier.py
  • scripts/classify_direct.py
  • scripts/reclassify_all.py
  • scripts/retry_errors.py
  • scripts/verification/verify_canonical_endpoints.py
  • start-stack.sh
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/canonical-endpoint-verification

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In load_env, the inline comment stripping heuristic (# preceded by whitespace) will also remove # characters that appear inside unquoted values (e.g., URLs or secrets); consider only stripping when the # occurs outside quotes or skipping this entirely for more predictable parsing.
  • In test_canonical_urls, skipped endpoints due to DNS/unreachable are currently counted as passed in total_passed, which makes the final summary ambiguous; consider not incrementing passed for skipped checks and adjusting the summary to clearly distinguish passed vs skipped.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `load_env`, the inline comment stripping heuristic (`#` preceded by whitespace) will also remove `#` characters that appear inside unquoted values (e.g., URLs or secrets); consider only stripping when the `#` occurs outside quotes or skipping this entirely for more predictable parsing.
- In `test_canonical_urls`, skipped endpoints due to DNS/unreachable are currently counted as passed in `total_passed`, which makes the final summary ambiguous; consider not incrementing `passed` for skipped checks and adjusting the summary to clearly distinguish passed vs skipped.

## Individual Comments

### Comment 1
<location path="scripts/verification/verify_canonical_endpoints.py" line_range="39-43" />
<code_context>
+                if not line or line.startswith("#"):
+                    continue
+                # Strip inline comments (but not inside quoted values)
+                if "#" in line:
+                    # Simple heuristic: split on first # that's preceded by whitespace
+                    for i, ch in enumerate(line):
+                        if ch == "#" and (i == 0 or line[i - 1] in " \t"):
+                            line = line[:i].strip()
+                            break
+                # Handle export prefix
</code_context>
<issue_to_address>
**issue:** Inline comment stripping may incorrectly truncate values that legitimately contain `#` characters.

This heuristic will strip `#` from legitimate env values (e.g., passwords, URLs with fragments, or quoted values that have whitespace before the `#`), corrupting them. Either detect and skip `#` characters that are inside quoted segments, or avoid inline comment stripping altogether and only treat lines starting with `#` as comments.
</issue_to_address>

### Comment 2
<location path="scripts/verification/verify_canonical_endpoints.py" line_range="390-393" />
<code_context>
+    print(f"\n── Canonical URLs ({public}) ──")
+
+    endpoints = [
+        ("/v1/models", "router models"),
+        ("/dashboard", "dashboard"),
+        ("/metrics", "metrics"),
+        ("/visualizer", "visualizer"),
+        ("/litellm/ui/", "LiteLLM admin UI"),
+        ("/langfuse", "Langfuse web UI"),
</code_context>
<issue_to_address>
**question (bug_risk):** Canonical router endpoints are probed without authentication, which may fail on secured deployments.

These checks call the public router URL without an `Authorization` header, while the router API in the same script uses a bearer token. On deployments where these endpoints require auth, they will always fail. Consider sending the router API key with these requests or restricting the checks to endpoints that are guaranteed to be publicly accessible.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/verification/verify_canonical_endpoints.py Outdated
Comment thread scripts/verification/verify_canonical_endpoints.py

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive canonical endpoint verification script, updates the README documentation, adds support for LiteLLM UI credentials and NextAuth URL configuration in the pod template, and hardens API response parsing across several benchmark and utility scripts. The review feedback focuses on improving robustness, including specifying explicit file encoding, tracking quotes when stripping inline comments in the env parser, defensively verifying that JSON responses are dictionaries before calling '.get()' to prevent potential AttributeErrors, and ensuring fallback defaults are applied when UI credential environment variables are empty.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

print(f" ⚠ env file not found: {path}")
return
loaded_files.append(str(path.name))
with open(path) as f:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Specify an explicit encoding (such as encoding="utf-8") when opening the environment file to prevent potential UnicodeDecodeError on systems with different default encodings.

Suggested change
with open(path) as f:
with open(path, encoding="utf-8") as f:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: added encoding=utf-8 to open(path) call.

Comment on lines +39 to +44
if "#" in line:
# Simple heuristic: split on first # that's preceded by whitespace
for i, ch in enumerate(line):
if ch == "#" and (i == 0 or line[i - 1] in " \t"):
line = line[:i].strip()
break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment indicates that inline comments should not be stripped if they are inside quoted values, but the current heuristic does not track quotes. If a password or API key contains a # character preceded by whitespace inside quotes, it will be incorrectly truncated. Update the parser to track whether the character is inside quotes before stripping.

Suggested change
if "#" in line:
# Simple heuristic: split on first # that's preceded by whitespace
for i, ch in enumerate(line):
if ch == "#" and (i == 0 or line[i - 1] in " \t"):
line = line[:i].strip()
break
if "#" in line:
in_quote = False
quote_char = None
for i, ch in enumerate(line):
if ch in ('"', "'"):
if not in_quote:
in_quote = True
quote_char = ch
elif ch == quote_char:
in_quote = False
quote_char = None
elif ch == "#" and not in_quote and (i == 0 or line[i - 1] in " \t"):
line = line[:i].strip()
break

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by removing inline comment stripping entirely. The heuristic was too fragile — our .env files don't use inline comments, so only full-line # comments are now skipped. This eliminates the entire class of quote-tracking bugs.

data = json.loads(resp.read())
choices = data.get("choices", [])
if not choices:
choices = data.get("choices")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure complete defensive parsing of the API response, verify that data is a dictionary before calling .get(). If the response is not a JSON object (e.g., if it is a list or primitive value), calling .get() will raise an AttributeError.

Suggested change
choices = data.get("choices")
if not isinstance(data, dict):
return "ERROR"
choices = data.get("choices")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: added isinstance(data, dict) guard before calling data.get('choices'). Also applied to classify_direct.py for global consistency.

Comment thread scripts/reclassify_all.py
data = json.loads(resp.read())
choices = data.get('choices', [])
if not choices:
choices = data.get('choices')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure complete defensive parsing of the API response, verify that data is a dictionary before calling .get(). If the response is not a JSON object, calling .get() will raise an AttributeError.

Suggested change
choices = data.get('choices')
if not isinstance(data, dict):
return "ERROR: empty response"
choices = data.get('choices')

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: added isinstance(data, dict) guard before calling data.get('choices').

Comment thread scripts/retry_errors.py
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read())
choices = data.get("choices", [])
choices = data.get("choices")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure complete defensive parsing of the API response, verify that data is a dictionary before calling .get(). If the response is not a JSON object, calling .get() will raise an AttributeError.

    if not isinstance(data, dict):
        return "ERROR"
    choices = data.get("choices")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: added isinstance(data, dict) guard before calling data.get('choices').

Comment thread start-stack.sh Outdated
Comment on lines +672 to +673
text = text.replace("LITELLM_UI_USERNAME_PLACEHOLDER", yaml_scalar(os.environ.get("UI_USERNAME", "admin")))
text = text.replace("LITELLM_UI_PASSWORD_PLACEHOLDER", yaml_scalar(os.environ.get("UI_PASSWORD", "admin")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If UI_USERNAME or UI_PASSWORD are exported as empty strings (e.g., UI_USERNAME=""), os.environ.get will return the empty string instead of falling back to the default "admin". Use or to ensure the default value is used when the environment variable is either missing or empty.

Suggested change
text = text.replace("LITELLM_UI_USERNAME_PLACEHOLDER", yaml_scalar(os.environ.get("UI_USERNAME", "admin")))
text = text.replace("LITELLM_UI_PASSWORD_PLACEHOLDER", yaml_scalar(os.environ.get("UI_PASSWORD", "admin")))
text = text.replace("LITELLM_UI_USERNAME_PLACEHOLDER", yaml_scalar(os.environ.get("UI_USERNAME") or "admin"))
text = text.replace("LITELLM_UI_PASSWORD_PLACEHOLDER", yaml_scalar(os.environ.get("UI_PASSWORD") or "admin"))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed: changed to os.environ.get('UI_USERNAME') or 'admin' so empty-string exports fall back to the default rather than injecting an empty YAML scalar.

- Drop fragile inline-comment stripping from env parser (Sourcery + Gemini):
  heuristic truncated '#' in passwords/URLs — only full-line comments now
- Add encoding='utf-8' to open() call (Gemini)
- Add isinstance(data, dict) guards before .get() calls in all 4 classifier
  scripts: benchmark_classifier, reclassify_all, retry_errors, classify_direct
- Use 'or' fallback for UI_USERNAME/UI_PASSWORD (Gemini):
  os.environ.get('X') or 'admin' handles empty-string exports
- Send auth headers on canonical GET endpoints (Sourcery):
  defense-in-depth even though router public endpoints don't require auth
- Fix exit condition: skipped DNS-unreachable endpoints no longer cause
  non-zero exit code (Sourcery high-level)
@sheepdestroyer

Copy link
Copy Markdown
Owner Author

All review comments addressed in 477d5e8:

Sourcery (2 inline + 2 high-level):

  • Inline comment stripping: removed entirely — only full-line # comments skipped now
  • Canonical GET without auth: added Authorization header to all canonical requests
  • Skipped endpoints as failures: exit condition now excludes skipped endpoints (total_tests - total_skipped)

Gemini Code Assist (5 inline):

  • encoding=utf-8: added to open() call
  • Quote tracking for #: resolved by removing inline comment stripping entirely
  • isinstance(data, dict) guards: added to benchmark_classifier, reclassify_all, retry_errors, and classify_direct.py (global consistency)
  • UI_USERNAME/UI_PASSWORD empty-string: changed to os.environ.get('X') or 'admin'

All fixes pushed to test/canonical-endpoint-verification.

@sheepdestroyer

Copy link
Copy Markdown
Owner Author

Closing in favor of a fresh PR with all review fixes applied. See #260.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant