Skip to content

chore: restore reverted test suites, folder structure, logic optimizations, and security patches (v2)#198

Closed
sheepdestroyer wants to merge 10 commits into
masterfrom
chore/restore-reversions-final-v2
Closed

chore: restore reverted test suites, folder structure, logic optimizations, and security patches (v2)#198
sheepdestroyer wants to merge 10 commits into
masterfrom
chore/restore-reversions-final-v2

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jul 1, 2026

Copy link
Copy Markdown
Owner

This PR comprehensively restores all files, directory organization, logic optimizations, container dependencies, and security patches that were mistakenly deleted or reverted during recent automated merge conflict resolutions on master.

1. Folder Structure & Reorganization Restored

  • Moved 23 test and utility files currently at the root back to their reorganized directories (tests/, scripts/, scripts/verification/, and router/tests/) matching PR Refactor: Reorganize repository structure #181.
  • Removed duplicate test files (e.g. removing router/test_memory_mcp.py and consolidating tests in router/tests/test_memory_mcp.py).

2. Missing Test and Utility Files Recovered

Checked out and restored the following 10 files from past commits in the repository history:

3. Logic Optimizations Re-applied in router/main.py

  • Async Cache: Restored the async annotations caching logic (_read_annotations_async and deepcopy offloading) to prevent blocking the event loop.
  • Token Heuristics: Restored the regex-based _count_tokens_heuristic and updated estimate_prompt_tokens to use it (bringing token estimation accuracy within ±11% of prose, code, and CJK).
  • Valkey URL Connection: Restored URL-based Redis/Valkey client initialization in get_redis to handle client configuration properly.

4. Security & Configuration Patches Restored

  • Category Injection: Restored input sanitization (urllib.parse.quote(category) and unquote) in router/memory_mcp.py to prevent parameter-injection vulnerabilities.
  • Password Placeholders: Restored pod.yaml and start-stack.sh back to secure, placeholder-based credential configurations (avoiding hardcoded default passwords in git).
  • Version Upgrades: Restored LiteLLM v1.90.2 and newer container tags (ClickHouse, Valkey, pgvector, MinIO, and Langfuse).
  • Container Dependencies: Fixed router/Dockerfile to include aiofiles.

5. CI Workflow & Test Collection Fixes

  • Dependency List: Added aiofiles to the pip install action in .github/workflows/test.yml.
  • Path Targets: Updated workflow execution steps to reference corrected tests/ and scripts/ pathways.
  • Import-time Execution: Wrapped tests/test_agy_behavior.py in a __main__ block to prevent immediate execution at import-time during test collection (resolving CI collection crash).

6. Agent Guidelines & Post-Mortem Documentation

  • Added Git Rebase and Conflict Resolution Rules to .agents/AGENTS.md to prevent automated bots from re-introducing folder and logic regressions.
  • Saved the complete post-mortem regression report to .agents/jules_regression_analysis.md.

Verification: All 176 unit and integration tests compile, collect, and pass successfully.

7. Review & Bot Reversion Updates Applied (v2)

  • Fixed missing comma in start-stack.sh python wrapper list rendering.
  • Restored performance-sensitive async _is_quota_exhausted log reader in router/agy_proxy.py and converted tests to async.
  • Restored parallel ThreadPoolExecutor optimization in scripts/benchmark_classifier.py.
  • Fixed test_detect_active_tool.py to import from module level main instead of router.main.
  • Defensively handled potential null statusCheckRollup in scripts/get_pr_status.py and added a regression unit test.
  • Expanded early openssl check in start-stack.sh to prevent command-not-found failures on unset env parameters.
  • Restored Prisma datetime/RobustDatetime serialization patch in litellm/entrypoint.py to fix spend logs serialization failures.
  • Restored complete environment-driven placeholders relocation in pod.yaml and start-stack.sh (for LITELLM_MASTER_KEY, POSTGRES_PASSWORD, OLLAMA_API_KEY, LANGFUSE_PUBLIC_KEY, and LANGFUSE_SECRET_KEY) together with dynamic fallback key generation.
  • Restored offload-aa-scores-sync optimization from PR ⚡ Offload synchronous AA scores loading to thread #98 to offload blocking synchronous disk operations to a background thread pool inside sync_adaptive_router_roster and get_best_free_model.
  • Added a comprehensive branch audit report at .agents/branch_audit_plan.md to track and prevent any future merge conflict regressions.

Summary by CodeRabbit

  • New Features

    • Improved router behavior for token estimation, Redis/Valkey connection handling, annotation loading, and model selection.
    • Added a PR status CLI and expanded benchmark scripts for token and classifier checks.
  • Bug Fixes

    • Better handling of quota limits, memory key parsing, and OAuth/session/status reads.
    • Updated container images and secret placeholders for more reliable stack setup.
  • Documentation

    • Added guidance for rebase-based conflict resolution and refreshed setup/test references.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 47 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: f0cbc5b2-ea3e-4ec0-97ec-21764dc305a7

📥 Commits

Reviewing files that changed from the base of the PR and between 660811f and ad44754.

📒 Files selected for processing (7)
  • router/main.py
  • router/memory_mcp.py
  • router/tests/test_get_redis.py
  • router/tests/test_load_persisted_stats.py
  • router/tests/test_memory_mcp.py
  • scripts/benchmark_classifier.py
  • scripts/benchmark_tokens.py
📝 Walkthrough

Walkthrough

This PR updates router async I/O, token estimation, Redis initialization, and AA-score loading; changes memory key encoding; moves PR status logic into scripts/; refreshes deployment pins, secrets, and CI paths; and adds new policy docs and test coverage.

Changes

Router async I/O and model heuristics

Layer / File(s) Summary
Async quota check
router/agy_proxy.py, router/tests/test_agy_proxy.py
_is_quota_exhausted becomes async, reads cli.log with aiofiles, and the streaming/non-streaming paths now await it.
VALKEY_URL Redis init
router/main.py, router/tests/test_get_redis.py
get_redis() prefers VALKEY_URL over host/port, with tests covering cached, cooldown, success, failure, and URL-based flows.
Regex token heuristic
router/main.py, router/tests/test_estimate_prompt_tokens.py
Prompt token estimation switches to regex-based weighted counting across string and block content.
AA scores and annotation caching
router/main.py, tests/test_read_annotations_async.py, tests/test_compute_free_model_score.py
AA score loading becomes lazy, annotation reads use async mtime-based caching, and the related tests are updated.
Router test coverage support
router/tests/test_detect_active_tool.py, router/tests/test_get_gemini_oauth_status.py, router/tests/test_get_goose_sessions.py, router/tests/test_load_persisted_stats.py
Adds new utility-function test suites covering tool detection, OAuth status, goose sessions, and persisted stats loading.
Router Docker dependency
router/Dockerfile
Adds aiofiles to the router image dependency installation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Memory MCP category encoding

Layer / File(s) Summary
Key encoding and validation
router/memory_mcp.py
Memory categories are URL-encoded in keys, decoded on parse, and non-string keys are rejected.
Memory MCP tests
router/tests/test_memory_mcp.py
Adds coverage for key creation, parsing, entry decoding, and key-format edge cases.

Estimated code review effort: 3 (Moderate) | ~25 minutes

CLI scripts and benchmarks

Layer / File(s) Summary
PR status script and tests
get_pr_status.py, scripts/get_pr_status.py, tests/test_get_pr_status.py
Removes the root helper, adds scripts/get_pr_status.py, and covers gh pr view output parsing and error handling.
Concurrent classifier benchmark
scripts/benchmark_classifier.py
Replaces the sequential benchmark loop with thread-pool based concurrent processing.
Token benchmark script
scripts/benchmark_tokens.py
Adds a standalone accuracy checker for estimate_prompt_tokens.
Scripts README paths
scripts/README.md
Updates integration test references to the new tests/ and scripts/ paths.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Deployment and CI configuration

Layer / File(s) Summary
Image pins and placeholders
README.md, pod.yaml
Refreshes LiteLLM, pgvector, ClickHouse, Langfuse, and MinIO pins and replaces hardcoded credentials with placeholders.
Secret bootstrap and templating
start-stack.sh
Expands secret generation, MinIO setup credentials, and pod.yaml placeholder substitution.
CI workflow paths
.github/workflows/test.yml
Updates dependency installation, test paths, and ignored filenames in the workflow.

Estimated code review effort: 4 (Complex) | ~45 minutes

Agent Git conflict policy documentation

Layer / File(s) Summary
AGENTS.md policy
.agents/AGENTS.md
Adds a Git rebase and conflict-resolution policy section.
Regression analysis doc
.agents/jules_regression_analysis.md
Adds a regression analysis document with Git strategy and guardrail guidance.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Prisma datetime serialization

Layer / File(s) Summary
Datetime serializer patch
litellm/entrypoint.py
Registers a Prisma serializer for datetime objects after the global datetime patch.

Estimated code review effort: 2 (Simple) | ~10 minutes

Test runner guard

Layer / File(s) Summary
Main guard
tests/test_agy_behavior.py
Wraps the test runner entry point behind a __main__ guard.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main theme: restoring tests, structure, optimizations, and security-related fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/restore-reversions-final-v2

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.

Sorry @sheepdestroyer, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@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 several enhancements, including updating the LiteLLM Gateway to v1.90.2, implementing secure placeholder-based configurations in pod.yaml and start-stack.sh, transitioning file operations to be asynchronous using aiofiles, and improving token estimation with a regex-based weighted heuristic. It also reorganizes and expands the test suite, parallelizes the classifier benchmark, and establishes a Git rebase and conflict resolution policy. Feedback on these changes highlights two key issues: first, newly generated credentials for Redis and ClickHouse in start-stack.sh are not exported or replaced in pod.yaml, leaving containers running with default credentials; second, a test mock setup in router/tests/test_agy_proxy.py incorrectly configures __aenter__ as a synchronous mock, preventing the test from properly validating the log-reading logic.

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.

Comment thread start-stack.sh
Comment on lines +144 to +154
if [ -z "$REDIS_AUTH" ]; then
REDIS_AUTH="$(openssl rand -hex 16)"
echo "REDIS_AUTH=\"$REDIS_AUTH\"" >> "$ENV_FILE"
echo "✓ Generated new REDIS_AUTH and saved to $ENV_FILE"
fi

if [ -z "$CLICKHOUSE_PASSWORD" ]; then
CLICKHOUSE_PASSWORD="$(openssl rand -hex 16)"
echo "CLICKHOUSE_PASSWORD=\"$CLICKHOUSE_PASSWORD\"" >> "$ENV_FILE"
echo "✓ Generated new CLICKHOUSE_PASSWORD and saved to $ENV_FILE"
fi

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.

security-medium medium

The script generates secure random values for REDIS_AUTH and CLICKHOUSE_PASSWORD and saves them to .env, but these variables are never exported in render_pod_yaml nor are there corresponding placeholders replaced in pod.yaml. As a result, the containers still run with the hardcoded default credentials (langfuse-redis-2026 and clickhouse) defined in pod.yaml.

To fully secure the stack, you should:

  1. Export REDIS_AUTH and CLICKHOUSE_PASSWORD in render_pod_yaml().
  2. Add REDIS_AUTH_PLACEHOLDER and CLICKHOUSE_PASSWORD_PLACEHOLDER to the placeholders list and perform the replacements.
  3. Update pod.yaml to use these placeholders instead of hardcoded values.

Comment on lines +85 to +87
mock_file = AsyncMock()
mock_file.readlines.return_value = ["line 1\n", "RESOURCE_EXHAUSTED info\n", "line 3\n"]
mock_open.return_value.__enter__.return_value = mock_file
mock_open.return_value.__aenter__.return_value = mock_file

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

In test_is_quota_exhausted_empty_reads_log, mock_open.return_value.__aenter__ is a synchronous MagicMock whose return_value is set to mock_file (an AsyncMock). When Python executes async with aiofiles.open(...) as f, it awaits the result of __aenter__(). Since mock_file is an AsyncMock, awaiting it resolves to mock_file.return_value (a new MagicMock), meaning f becomes mock_file.return_value rather than mock_file itself. Consequently, await f.readlines() calls mock_file.return_value.readlines() instead of the configured mock_file.readlines(), returning a default mock rather than the mock lines.

The test only passes because _is_quota_exhausted falls through to return True on line 164 when no match is found. To make the test actually verify the log-reading logic, configure __aenter__ as an AsyncMock that returns mock_file directly.

Suggested change
mock_file = AsyncMock()
mock_file.readlines.return_value = ["line 1\n", "RESOURCE_EXHAUSTED info\n", "line 3\n"]
mock_open.return_value.__enter__.return_value = mock_file
mock_open.return_value.__aenter__.return_value = mock_file
mock_file = AsyncMock()
mock_file.readlines.return_value = ["line 1\n", "RESOURCE_EXHAUSTED info\n", "line 3\n"]
mock_open.return_value.__aenter__ = AsyncMock(return_value=mock_file)

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
router/memory_mcp.py (1)

52-58: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don't decode legacy stored keys without a format/version boundary.

_parse_key() now unquotes every stored category. That changes the meaning of any pre-existing raw key whose category already contains %HH sequences (for example, release%2Fnotes now reads back as release/notes). Because these keys are persisted, this is a storage-format break, not just an internal refactor. Please add a versioned key format or an explicit migration/back-compat path before switching reads to unconditional unquote().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/memory_mcp.py` around lines 52 - 58, The _parse_key() change in
memory_mcp.py is decoding all stored categories unconditionally, which breaks
legacy persisted keys that already contain literal %HH sequences. Add a
versioned format boundary or explicit backward-compatible parsing in
_parse_key() and the related key writer so new keys can be unquoted safely while
old keys keep their original category values. Use the existing structured key
parsing around _parse_key() to detect the format and only apply
urllib.parse.unquote() for the new version or migrated entries.
router/main.py (1)

3947-3964: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Invalidate or refresh the annotations cache after a successful write.

save_annotations() merges from _read_annotations_async()'s cached snapshot, but the cache is left untouched after _atomic_write_json_async(...). On filesystems or mounted volumes with coarse mtime resolution, two saves in the same timestamp quantum can reload the pre-write snapshot and drop the earlier update on the next merge.

Proposed fix
         async with annotations_lock:
             if ann_path.exists():
                 try:
                     existing = await _read_annotations_async(str(ann_path))
                 except Exception as read_err:
                     logger.warning(
                         f"Could not read existing annotations: {read_err}. Overwriting."
                     )

             # Merge new annotations into existing
             for k, item in data.items():
                 # For partial updates, merge only fields provided in the request
                 update_data = item.model_dump(exclude_unset=True)
                 if k in existing and isinstance(existing[k], dict):
                     existing[k].update(update_data)
                 else:
                     existing[k] = item.model_dump()
             await _atomic_write_json_async(str(ann_path), existing)
+            _annotations_cache.pop(str(ann_path), None)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/main.py` around lines 3947 - 3964, The save_annotations() flow in
router/main.py updates the on-disk annotations via _atomic_write_json_async but
leaves the _read_annotations_async cache stale, which can cause the next merge
to reuse an old snapshot. After a successful write, invalidate or refresh the
annotations cache inside the annotations_lock section so subsequent calls to
_read_annotations_async see the newly persisted data; use the existing
save_annotations(), _read_annotations_async(), and _atomic_write_json_async()
symbols to place the cache refresh in the right spot.
🧹 Nitpick comments (4)
.agents/AGENTS.md (1)

25-28: 📐 Maintainability & Code Quality | 🔵 Trivial

Use a stable baseline for the test-count check.

“Equal to or greater than before the resolution” is noisy unless the baseline is recorded explicitly; test counts can change legitimately in the same PR. Tie this to a pre-rebase CI run or a fixed test matrix instead.

Proposed wording
- 4. **Enforce Test Suite Count**: Run the full unit test suite (`pytest`) after conflict resolution. Verify that the total number of passing tests is equal to or greater than before the resolution.
+ 4. **Enforce Test Suite Baseline**: Run the full unit test suite (`pytest`) after conflict resolution, and compare the result against a recorded pre-rebase baseline or the same CI test matrix.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/AGENTS.md around lines 25 - 28, The test-count guidance in AGENTS.md
needs a stable baseline, since “equal to or greater than before the resolution”
is ambiguous without a recorded reference point. Update the instruction around
pytest/test-suite verification to require comparing against a pre-rebase CI run
or another fixed baseline, and make sure the wording in the relevant AGENTS.md
section remains tied to the existing rebase-and-test workflow.
.agents/jules_regression_analysis.md (1)

34-35: 📐 Maintainability & Code Quality | 🔵 Trivial

Tone down the rename-tracking guarantee.

git rebase helps with moved files, but it does not automatically remap every rename/delete or rename/rename conflict. This wording is too strong and could cause agents to skip a manual check when Git still needs one.

Proposed wording
- * **Rename Tracking**: Git's rename tracking algorithm handles moves seamlessly during a rebase. If a file was renamed from `test_a2_verify.py` to `tests/test_a2_verify.py` on `master`, Git will automatically apply the feature branch's modifications to `tests/test_a2_verify.py` instead of leaving them at the root.
+ * **Rename Tracking**: Git's rename detection usually helps map edits onto renamed paths during a rebase, but conflicts still need manual inspection when heuristics do not line up.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/jules_regression_analysis.md around lines 34 - 35, Tone down the
claim in the “Rename Tracking” part of the rebase explanation: the current
wording overstates what git rebase and Git’s rename tracking can do, so revise
the text in the affected section of jules_regression_analysis.md to say rebasing
may carry changes across moved files but still requires manual review for
rename/delete and rename/rename conflicts. Keep the guidance aligned with the
“Why it works” and “Rename Tracking” bullets, and avoid any language that
implies Git will always remap renames automatically.
tests/test_host_agy_daemon.py (1)

263-284: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Global os.environ.copy patch is fragile but functionally scoped by monkeypatch.

Patching copy directly on the live os.environ singleton works via monkeypatch teardown, but it's a slightly indirect way to stub environment reads; a monkeypatch.setenv/dict-based approach on the actual env var would be more conventional. Not blocking.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_host_agy_daemon.py` around lines 263 - 284, The test relies on
patching the live os.environ.copy method, which is a fragile way to simulate the
environment in test_daemon_post_stream_false_no_model_override. Update the setup
to use a conventional environment override approach (for example, via
monkeypatch.setenv or a controlled env mapping) so the subprocess path in
host_agy_daemon.asyncio.create_subprocess_exec still sees the expected
environment without directly stubbing os.environ.copy.
router/tests/test_memory_mcp.py (1)

31-85: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add one round-trip case that actually exercises the encoded-category contract.

All _make_key() assertions still use categories that do not need escaping, and the only colon-containing _parse_key() case asserts the legacy truncated form. A regression where _make_key() stops quoting or _parse_key() stops unquoting would still pass this suite. Please add a case such as proj:alpha/100% ready and assert the stored key contains the encoded segment while _parse_key() returns the original category.

Also applies to: 240-290

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/tests/test_memory_mcp.py` around lines 31 - 85, Add a round-trip test
in test_make_key_* or a new test around _make_key and _parse_key that uses a
category needing escaping, such as proj:alpha/100% ready, and verify the
generated key stores the encoded category segment while _parse_key returns the
original unescaped category. Reuse the existing _make_key and _parse_key symbols
so the test covers the encoded-category contract and catches regressions in both
quoting and unquoting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@router/tests/test_get_redis.py`:
- Around line 41-45: The host/port-path tests for get_redis are still affected
by an existing VALKEY_URL, so they may accidentally take the URL branch instead
of the intended VALKEY_HOST/VALKEY_PORT path. Update the affected tests in
test_get_redis.py to explicitly clear or override VALKEY_URL alongside the
existing `@patch.dict` setup, so the assertions always exercise the host/port
logic in router.main.get_redis regardless of the surrounding environment.

In `@router/tests/test_load_persisted_stats.py`:
- Around line 1-6: The test module imports router.main before setting up
CONFIG_PATH, which makes collection order-dependent because router.main reads
the config at import time. Update the test setup to define CONFIG_PATH before
importing router.main, or move the import into the test after the env setup; use
the existing load_persisted_stats import and router.main module reference to
keep the test isolated.

In `@router/tests/test_memory_mcp.py`:
- Around line 143-152: The memory parser tests contain duplicate function names,
so pytest overwrites earlier cases and skips coverage for the invalid JSON and
None inputs. Rename the repeated test functions in the test module so each
scenario has a unique name, keeping the assertions for _parse_memory_value and
ensuring both the "{invalid_json:" and None cases are collected and run.

In `@scripts/benchmark_classifier.py`:
- Around line 60-63: The expected-tier selection in process_item currently
prefers llm_tier before clf_tier, which can benchmark new-schema records against
the wrong label when both are present. Update the fallback order in process_item
so clf_tier is chosen before llm_tier, keeping tier as the primary value and
preserving the intended legacy/new-schema behavior used by retry_errors and
reclassify_all.

In `@scripts/benchmark_tokens.py`:
- Around line 68-75: The benchmark failure path in verify_accuracy() currently
relies on an assert, which can be stripped under optimized Python runs and let
failures exit successfully. Update verify_accuracy() and the __main__ block in
scripts/benchmark_tokens.py so failures use an explicit path such as returning a
boolean or calling sys.exit(1), and make sure the exception handling around
verify_accuracy() preserves a non-zero exit when token estimation accuracy does
not pass.

In `@start-stack.sh`:
- Around line 144-154: The generated REDIS_AUTH and CLICKHOUSE_PASSWORD values
are written to .env but never propagated into the pod manifest, so the deployed
containers still use fixed credentials. Update render_pod_yaml() to substitute
these secret values from the environment, and change the hardcoded Redis/Valkey
and ClickHouse password fields in pod.yaml to use the new placeholders so the
Valkey-LF and Langfuse/ClickHouse containers receive the generated secrets.

---

Outside diff comments:
In `@router/main.py`:
- Around line 3947-3964: The save_annotations() flow in router/main.py updates
the on-disk annotations via _atomic_write_json_async but leaves the
_read_annotations_async cache stale, which can cause the next merge to reuse an
old snapshot. After a successful write, invalidate or refresh the annotations
cache inside the annotations_lock section so subsequent calls to
_read_annotations_async see the newly persisted data; use the existing
save_annotations(), _read_annotations_async(), and _atomic_write_json_async()
symbols to place the cache refresh in the right spot.

In `@router/memory_mcp.py`:
- Around line 52-58: The _parse_key() change in memory_mcp.py is decoding all
stored categories unconditionally, which breaks legacy persisted keys that
already contain literal %HH sequences. Add a versioned format boundary or
explicit backward-compatible parsing in _parse_key() and the related key writer
so new keys can be unquoted safely while old keys keep their original category
values. Use the existing structured key parsing around _parse_key() to detect
the format and only apply urllib.parse.unquote() for the new version or migrated
entries.

---

Nitpick comments:
In @.agents/AGENTS.md:
- Around line 25-28: The test-count guidance in AGENTS.md needs a stable
baseline, since “equal to or greater than before the resolution” is ambiguous
without a recorded reference point. Update the instruction around
pytest/test-suite verification to require comparing against a pre-rebase CI run
or another fixed baseline, and make sure the wording in the relevant AGENTS.md
section remains tied to the existing rebase-and-test workflow.

In @.agents/jules_regression_analysis.md:
- Around line 34-35: Tone down the claim in the “Rename Tracking” part of the
rebase explanation: the current wording overstates what git rebase and Git’s
rename tracking can do, so revise the text in the affected section of
jules_regression_analysis.md to say rebasing may carry changes across moved
files but still requires manual review for rename/delete and rename/rename
conflicts. Keep the guidance aligned with the “Why it works” and “Rename
Tracking” bullets, and avoid any language that implies Git will always remap
renames automatically.

In `@router/tests/test_memory_mcp.py`:
- Around line 31-85: Add a round-trip test in test_make_key_* or a new test
around _make_key and _parse_key that uses a category needing escaping, such as
proj:alpha/100% ready, and verify the generated key stores the encoded category
segment while _parse_key returns the original unescaped category. Reuse the
existing _make_key and _parse_key symbols so the test covers the
encoded-category contract and catches regressions in both quoting and unquoting.

In `@tests/test_host_agy_daemon.py`:
- Around line 263-284: The test relies on patching the live os.environ.copy
method, which is a fragile way to simulate the environment in
test_daemon_post_stream_false_no_model_override. Update the setup to use a
conventional environment override approach (for example, via monkeypatch.setenv
or a controlled env mapping) so the subprocess path in
host_agy_daemon.asyncio.create_subprocess_exec still sees the expected
environment without directly stubbing os.environ.copy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f168cf5-5a02-4709-9b93-1b3921da5252

📥 Commits

Reviewing files that changed from the base of the PR and between c304f3d and 36fa94a.

⛔ Files ignored due to path filters (1)
  • debug-homepage.png is excluded by !**/*.png
📒 Files selected for processing (49)
  • .agents/AGENTS.md
  • .agents/jules_regression_analysis.md
  • .github/workflows/test.yml
  • README.md
  • get_pr_status.py
  • pod.yaml
  • router/Dockerfile
  • router/agy_proxy.py
  • router/main.py
  • router/memory_mcp.py
  • router/test_memory_mcp.py
  • router/tests/test_agy_proxy.py
  • router/tests/test_detect_active_tool.py
  • router/tests/test_estimate_prompt_tokens.py
  • router/tests/test_get_gemini_oauth_status.py
  • router/tests/test_get_goose_sessions.py
  • router/tests/test_get_redis.py
  • router/tests/test_load_persisted_stats.py
  • router/tests/test_memory_mcp.py
  • scripts/README.md
  • scripts/benchmark_classifier.py
  • scripts/benchmark_tokens.py
  • scripts/get_pr_status.py
  • scripts/host_agy_daemon.py
  • scripts/sync_gemini_token.py
  • scripts/test_quota_reset.sh
  • scripts/verification/verify_breaker.py
  • scripts/watch_quota.sh
  • start-stack.sh
  • test_memory_mcp.py
  • tests/test_a2_verify.py
  • tests/test_agy_behavior.py
  • tests/test_agy_tiers.py
  • tests/test_antigravity.py
  • tests/test_atomic_write.py
  • tests/test_check_http_endpoint.py
  • tests/test_circuit_breaker.py
  • tests/test_classifier_accuracy.py
  • tests/test_compute_free_model_score.py
  • tests/test_get_pr_status.py
  • tests/test_host_agy_daemon.py
  • tests/test_map_tool_to_category.py
  • tests/test_models_proxy.py
  • tests/test_pie_chart_gradient.py
  • tests/test_read_annotations_async.py
  • tests/test_record_tool_usage.py
  • tests/test_src_badge.py
  • tests/test_stream_latency.py
  • tests/test_sync_gemini_token.py
💤 Files with no reviewable changes (3)
  • test_memory_mcp.py
  • router/test_memory_mcp.py
  • get_pr_status.py

Comment thread router/tests/test_get_redis.py
Comment thread router/tests/test_load_persisted_stats.py
Comment thread router/tests/test_memory_mcp.py
Comment thread scripts/benchmark_classifier.py
Comment thread scripts/benchmark_tokens.py Outdated
Comment thread start-stack.sh
Comment on lines +144 to +154
if [ -z "$REDIS_AUTH" ]; then
REDIS_AUTH="$(openssl rand -hex 16)"
echo "REDIS_AUTH=\"$REDIS_AUTH\"" >> "$ENV_FILE"
echo "✓ Generated new REDIS_AUTH and saved to $ENV_FILE"
fi

if [ -z "$CLICKHOUSE_PASSWORD" ]; then
CLICKHOUSE_PASSWORD="$(openssl rand -hex 16)"
echo "CLICKHOUSE_PASSWORD=\"$CLICKHOUSE_PASSWORD\"" >> "$ENV_FILE"
echo "✓ Generated new CLICKHOUSE_PASSWORD and saved to $ENV_FILE"
fi

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Generated Redis/ClickHouse secrets never reach the deployed pod.

REDIS_AUTH and CLICKHOUSE_PASSWORD are now bootstrapped into .env, but render_pod_yaml() never substitutes them, and pod.yaml still uses hardcoded langfuse-redis-2026 / clickhouse values for the Valkey-LF and Langfuse/ClickHouse containers. That leaves the stack on fixed credentials even though this script reports fresh secrets were generated.

Suggested wiring
 render_pod_yaml() {
-    export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY LANGFUSE_INIT_USER_PASSWORD MINIO_ROOT_USER MINIO_ROOT_PASSWORD
+    export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY LANGFUSE_INIT_USER_PASSWORD MINIO_ROOT_USER MINIO_ROOT_PASSWORD REDIS_AUTH CLICKHOUSE_PASSWORD
     python3 - "$WORKDIR/pod.yaml" <<'PY'
 ...
     "postgres-password-***",
     "MINIO_USER_PLACEHOLDER",
     "MINIO_PASSWORD_PLACEHOLDER",
-    "LANGFUSE_INIT_USER_PASSWORD_PLACEHOLDER"
+    "LANGFUSE_INIT_USER_PASSWORD_PLACEHOLDER",
+    "REDIS_AUTH_PLACEHOLDER",
+    "CLICKHOUSE_PASSWORD_PLACEHOLDER",
 ]
 ...
 text = text.replace("MINIO_USER_PLACEHOLDER", os.environ["MINIO_ROOT_USER"])
 text = text.replace("MINIO_PASSWORD_PLACEHOLDER", os.environ["MINIO_ROOT_PASSWORD"])
 text = text.replace("LANGFUSE_INIT_USER_PASSWORD_PLACEHOLDER", os.environ["LANGFUSE_INIT_USER_PASSWORD"])
+text = text.replace("REDIS_AUTH_PLACEHOLDER", os.environ["REDIS_AUTH"])
+text = text.replace("CLICKHOUSE_PASSWORD_PLACEHOLDER", os.environ["CLICKHOUSE_PASSWORD"])

Also swap the corresponding hardcoded values in pod.yaml to those placeholders.

Also applies to: 384-421

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@start-stack.sh` around lines 144 - 154, The generated REDIS_AUTH and
CLICKHOUSE_PASSWORD values are written to .env but never propagated into the pod
manifest, so the deployed containers still use fixed credentials. Update
render_pod_yaml() to substitute these secret values from the environment, and
change the hardcoded Redis/Valkey and ClickHouse password fields in pod.yaml to
use the new placeholders so the Valkey-LF and Langfuse/ClickHouse containers
receive the generated secrets.

@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.

New security issues found

Comment thread scripts/get_pr_status.py

def run_cmd(argv: Sequence[str]) -> str:
"""Runs a command and returns stripped stdout."""
result = subprocess.run(argv, shell=False, capture_output=True, text=True, check=True, timeout=30)

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.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
router/main.py (1)

3928-3937: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use a stronger cache signature for annotation reads.

Line 3928 invalidates only on float mtime; on coarse or rapid same-tick writes, _read_annotations_async() can return stale JSON and cause save_annotations() to merge from an old copy. Use os.stat() with st_mtime_ns and size, and refresh the stored signature after reading.

Proposed fix
-    current_mtime = await asyncio.to_thread(os.path.getmtime, path)
+    current_stat = await asyncio.to_thread(os.stat, path)
+    current_signature = (current_stat.st_mtime_ns, current_stat.st_size)
 
     cache_entry = _annotations_cache.get(path)
 
-    if cache_entry is None or current_mtime != cache_entry["mtime"]:
+    if cache_entry is None or current_signature != cache_entry["signature"]:
         async with aiofiles.open(path, "r", encoding="utf-8") as f:
             # Read asynchronously, but parse in a thread pool to avoid blocking event loop
             content = await f.read()
             data = await asyncio.to_thread(json.loads, content)
-            _annotations_cache[path] = {"mtime": current_mtime, "data": data}
+            updated_stat = await asyncio.to_thread(os.stat, path)
+            _annotations_cache[path] = {
+                "signature": (updated_stat.st_mtime_ns, updated_stat.st_size),
+                "data": data,
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/main.py` around lines 3928 - 3937, The annotation cache in
`_read_annotations_async()` is only keyed by `mtime`, so it can miss rapid or
same-tick updates and return stale JSON. Update the cache signature to use
`os.stat()` with `st_mtime_ns` plus file size, and compare that signature
against the stored cache entry before reusing data. After successfully reading
and parsing the file, store the refreshed signature alongside the parsed
annotations in `_annotations_cache` so `save_annotations()` always merges
against the latest copy.
pod.yaml (1)

299-310: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Seed the Langfuse project API keys during init

start-stack.sh already generates LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY, but this init block only creates the org, project, and user. Add the project key envs so a fresh Langfuse instance creates the same keys the router uses.

Proposed wiring
     - name: LANGFUSE_INIT_PROJECT_ID
       value: proj-triage-gateway-id
     - name: LANGFUSE_INIT_PROJECT_NAME
       value: Triage Gateway
+    - name: LANGFUSE_INIT_PROJECT_PUBLIC_KEY
+      value: LANGFUSE_PUBLIC_KEY_PLACEHOLDER
+    - name: LANGFUSE_INIT_PROJECT_SECRET_KEY
+      value: LANGFUSE_SECRET_KEY_PLACEHOLDER
     - name: LANGFUSE_INIT_USER_EMAIL
       value: admin@local.dev
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pod.yaml` around lines 299 - 310, The Langfuse init block currently seeds
only the org, project, and user, so a fresh instance misses the API keys that
start-stack.sh already generates. Update the init env list in pod.yaml alongside
LANGFUSE_INIT_PROJECT_ID/NAME and LANGFUSE_INIT_USER_EMAIL/PASSWORD to also seed
the project API key variables used by the router, keeping the names consistent
with the existing LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY wiring.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@litellm/entrypoint.py`:
- Around line 107-118: The Prisma serializer setup in entrypoint startup is
swallowing all failures, which hides broken compatibility patches. Update the
try/except around prisma.builder.serializer registration to log the caught
exception with context instead of using a bare pass, and keep the diagnostic
tied to the _serialize_dt registration path. If the import is mandatory for this
startup path, fail fast after logging; only treat the Prisma import as optional
if that is truly intended.

In `@start-stack.sh`:
- Around line 209-215: The interactive OLLAMA_API_KEY prompt in start-stack.sh
currently accepts an empty response and persists it, so update the input flow
after read -rs OLLAMA_API_KEY to reject blank values before the echo >>
"$ENV_FILE" append. Add a simple non-empty check in the same OLLAMA_API_KEY
branch, re-prompt or abort when the user just presses Enter, and only write the
key once a valid value is entered.
- Around line 470-471: The PostgreSQL password encoding in the startup script is
leaving reserved characters like / unescaped, which can break the DSN when
substituted into the postgresql://postgres:...@... userinfo. Update the password
encoding in the code that builds encoded_password to call urllib.parse.quote
with safe="" so all special characters are percent-encoded before replacing
POSTGRES_PASSWORD_ENCODED_PLACEHOLDER.

---

Outside diff comments:
In `@pod.yaml`:
- Around line 299-310: The Langfuse init block currently seeds only the org,
project, and user, so a fresh instance misses the API keys that start-stack.sh
already generates. Update the init env list in pod.yaml alongside
LANGFUSE_INIT_PROJECT_ID/NAME and LANGFUSE_INIT_USER_EMAIL/PASSWORD to also seed
the project API key variables used by the router, keeping the names consistent
with the existing LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY wiring.

In `@router/main.py`:
- Around line 3928-3937: The annotation cache in `_read_annotations_async()` is
only keyed by `mtime`, so it can miss rapid or same-tick updates and return
stale JSON. Update the cache signature to use `os.stat()` with `st_mtime_ns`
plus file size, and compare that signature against the stored cache entry before
reusing data. After successfully reading and parsing the file, store the
refreshed signature alongside the parsed annotations in `_annotations_cache` so
`save_annotations()` always merges against the latest copy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4c59826-f971-4318-a3a3-7be8c20c7bae

📥 Commits

Reviewing files that changed from the base of the PR and between 36fa94a and 660811f.

📒 Files selected for processing (6)
  • .agents/branch_audit_plan.md
  • litellm/entrypoint.py
  • pod.yaml
  • router/main.py
  • start-stack.sh
  • tests/test_compute_free_model_score.py
✅ Files skipped from review due to trivial changes (1)
  • .agents/branch_audit_plan.md

Comment thread litellm/entrypoint.py
Comment on lines +107 to +118
try:
from prisma.builder import serializer
def _serialize_dt(dt):
"""Serialize datetime to ISO8601 with timezone (UTC if naive)."""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.isoformat()
serializer.register(original_datetime, _serialize_dt)
serializer.register(RobustDatetime, _serialize_dt)
print("🩹 Registered original_datetime + RobustDatetime with Prisma serializer")
except Exception:
pass

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t silently ignore serializer registration failures.

This is the core compatibility patch; if the import or registration fails, startup continues without the serializer and Prisma datetime writes can fail later with no diagnostic. Log the exception at minimum, and consider failing fast for registration errors while only tolerating an explicitly optional Prisma import.

Proposed fix
 try:
     from prisma.builder import serializer
+except ImportError as exc:
+    print(f"⚠️ Prisma serializer unavailable: {exc}", file=sys.stderr)
+else:
     def _serialize_dt(dt):
         """Serialize datetime to ISO8601 with timezone (UTC if naive)."""
         if dt.tzinfo is None:
             dt = dt.replace(tzinfo=timezone.utc)
         return dt.isoformat()
     serializer.register(original_datetime, _serialize_dt)
     serializer.register(RobustDatetime, _serialize_dt)
     print("🩹 Registered original_datetime + RobustDatetime with Prisma serializer")
-except Exception:
-    pass
+except Exception as exc:
+    print(f"❌ Failed to register Prisma datetime serializer: {exc}", file=sys.stderr)
+    raise
 sys.stdout.flush()
🧰 Tools
🪛 Ruff (0.15.20)

[error] 117-118: try-except-pass detected, consider logging the exception

(S110)


[warning] 117-117: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@litellm/entrypoint.py` around lines 107 - 118, The Prisma serializer setup in
entrypoint startup is swallowing all failures, which hides broken compatibility
patches. Update the try/except around prisma.builder.serializer registration to
log the caught exception with context instead of using a bare pass, and keep the
diagnostic tied to the _serialize_dt registration path. If the import is
mandatory for this startup path, fail fast after logging; only treat the Prisma
import as optional if that is truly intended.

Source: Linters/SAST tools

Comment thread start-stack.sh
Comment on lines +209 to +215
if [ -z "$OLLAMA_API_KEY" ]; then
if [ -t 0 ]; then
echo "🔑 OLLAMA_API_KEY not found."
echo -n "Please enter your Ollama API Key (input will be hidden): "
read -rs OLLAMA_API_KEY
echo ""
echo "OLLAMA_API_KEY=\"$OLLAMA_API_KEY\"" >> "$ENV_FILE"

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject empty interactive Ollama keys before persisting.

Line 213 allows pressing Enter, then line 215 saves an empty key; the pod later renders that blank value into both LiteLLM and router envs, breaking Ollama-authenticated routing.

Proposed fix
         echo -n "Please enter your Ollama API Key (input will be hidden): "
         read -rs OLLAMA_API_KEY
         echo ""
+        if [ -z "$OLLAMA_API_KEY" ]; then
+            echo "❌ Error: OLLAMA_API_KEY cannot be empty." >&2
+            exit 1
+        fi
         echo "OLLAMA_API_KEY=\"$OLLAMA_API_KEY\"" >> "$ENV_FILE"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -z "$OLLAMA_API_KEY" ]; then
if [ -t 0 ]; then
echo "🔑 OLLAMA_API_KEY not found."
echo -n "Please enter your Ollama API Key (input will be hidden): "
read -rs OLLAMA_API_KEY
echo ""
echo "OLLAMA_API_KEY=\"$OLLAMA_API_KEY\"" >> "$ENV_FILE"
if [ -z "$OLLAMA_API_KEY" ]; then
if [ -t 0 ]; then
echo "🔑 OLLAMA_API_KEY not found."
echo -n "Please enter your Ollama API Key (input will be hidden): "
read -rs OLLAMA_API_KEY
echo ""
if [ -z "$OLLAMA_API_KEY" ]; then
echo "❌ Error: OLLAMA_API_KEY cannot be empty." >&2
exit 1
fi
echo "OLLAMA_API_KEY=\"$OLLAMA_API_KEY\"" >> "$ENV_FILE"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@start-stack.sh` around lines 209 - 215, The interactive OLLAMA_API_KEY prompt
in start-stack.sh currently accepts an empty response and persists it, so update
the input flow after read -rs OLLAMA_API_KEY to reject blank values before the
echo >> "$ENV_FILE" append. Add a simple non-empty check in the same
OLLAMA_API_KEY branch, re-prompt or abort when the user just presses Enter, and
only write the key once a valid value is entered.

Comment thread start-stack.sh
Comment on lines +470 to +471
encoded_password = urllib.parse.quote(os.environ['POSTGRES_PASSWORD'])
text = text.replace("POSTGRES_PASSWORD_ENCODED_PLACEHOLDER", encoded_password)

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from urllib.parse import quote
assert quote("a/b") == "a/b"
assert quote("a/b", safe="") == "a%2Fb"
print("urllib.parse.quote safe behavior verified")
PY

Repository: sheepdestroyer/LLM-Routing

Length of output: 206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section and nearby DSN construction in start-stack.sh
sed -n '440,490p' start-stack.sh

Repository: sheepdestroyer/LLM-Routing

Length of output: 2675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the encoded password placeholder is used, and inspect the DSN template around it.
rg -n "POSTGRES_PASSWORD_ENCODED_PLACEHOLDER|postgres|dsn|user=.*password|://.*POSTGRES_PASSWORD" start-stack.sh pod.yaml router -S

Repository: sheepdestroyer/LLM-Routing

Length of output: 1475


Encode the Postgres password as userinfo.
urllib.parse.quote() leaves / unescaped by default, but this value is inserted into postgresql://postgres:...@..., so a password containing / can break the DSN. Use safe="" here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@start-stack.sh` around lines 470 - 471, The PostgreSQL password encoding in
the startup script is leaving reserved characters like / unescaped, which can
break the DSN when substituted into the postgresql://postgres:...@... userinfo.
Update the password encoding in the code that builds encoded_password to call
urllib.parse.quote with safe="" so all special characters are percent-encoded
before replacing POSTGRES_PASSWORD_ENCODED_PLACEHOLDER.

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