Skip to content

fix(p3): remediate top audit findings#66

Closed
KooshaPari wants to merge 1 commit into
mainfrom
fix/p3-rem-phenoAI-6
Closed

fix(p3): remediate top audit findings#66
KooshaPari wants to merge 1 commit into
mainfrom
fix/p3-rem-phenoAI-6

Conversation

@KooshaPari

@KooshaPari KooshaPari commented Jun 29, 2026

Copy link
Copy Markdown
Owner

User description

Summary

Remediates three P3 findings from audit-run-v37 in phenoAI:

1. Integrate tracing instrumentation (L5 Observability)

The tracing = "0.1" dependency was declared but never used. Now integrated:

  • #[tracing::instrument(skip(...))] on OpenAiProvider::complete()
  • tracing::info! on successful completions and routing decisions
  • tracing::error! on provider failures
  • tracing::warn! on transient errors and retries

2. Enforce HTTP timeout (L26 Resilience)

LlmError::Timeout and CompletionRequest::timeout_ms existed but were never enforced:

  • reqwest::Client now built with ClientBuilder::timeout(120s)
  • Per-request timeout_ms applied via .timeout(Duration::from_millis(ms))
  • Reqwest timeout errors correctly mapped to LlmError::Timeout { timeout_ms }

3. Retry with exponential backoff + recovery hints (L26/L14)

  • New LlmRouter::complete_with_retry() — 3 attempts with 100/200/400ms backoff
  • LlmError::is_retryable() — only retries Provider/RateLimited errors
  • LlmError::recovery_hint() — human-readable recovery suggestion per variant
  • Error variants enriched: RateLimited { retry_after_ms }, Timeout { timeout_ms }

Testing

  • 6 new unit tests covering retry logic, retryability classification, recovery hints, and tracing event emission
  • All 27 tests pass
  • Clippy: 0 warnings
  • Build: clean

CodeAnt-AI Description

Fix LLM routing errors, timeouts, and retry behavior

What Changed

  • Error messages now include helpful details for rate limits and timeouts, instead of plain one-word failures
  • Requests to the OpenAI provider now stop after a timeout, and timeout settings can be applied per request
  • Temporary failures are retried up to 3 times before failing, while invalid model names fail immediately
  • Tracing events are emitted for routing decisions, successful completions, failures, and retries
  • Tests now cover error messages, retry behavior, retry rules, recovery hints, and tracing output

Impact

✅ Fewer stalled AI requests
✅ Clearer error messages for failed completions
✅ Faster recovery from temporary provider failures

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Three concrete fixes addressing audit L5 (observability), L26 (resilience),
and L14 (error recovery hints):

1. Integrate tracing instrumentation (L5)
   - Added #[tracing::instrument] to OpenAiProvider::complete()
   - Added tracing::info!/error!/warn! events for routing decisions,
     provider errors, and retry lifecycle
   - tracing = "0.1" dep is now used, fixing dead-dependency lint

2. Enforce timeout on HTTP requests (L26)
   - Client built with reqwest::ClientBuilder and 120s default timeout
   - Per-request timeout applied from CompletionRequest::timeout_ms
   - Reqwest timeout errors correctly mapped to LlmError::Timeout
   - Added  test

3. Add retry with exponential backoff (L26) and recovery hints (L14)
   - LlmRouter::complete_with_retry with 3 attempts: 100ms, 200ms, 400ms
   - Only retries on transient (Provider, RateLimited) errors
   - LlmError::is_retryable() and LlmError::recovery_hint() methods
   - Error variants enriched with structured fields instead of unit variants

Tests added:
  - complete_with_retry_succeeds_on_first_try
  - complete_with_retry_exhausts_after_max_attempts
  - complete_with_retry_does_not_retry_non_retryable_errors
  - llm_error_recovery_hints_are_distinct
  - llm_error_is_retryable_classification
  - tracing_events_are_emitted_on_routing_decision
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@codeant-ai

codeant-ai Bot commented Jun 29, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@codeant-ai

codeant-ai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@KooshaPari, 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d0f203f-afe9-46c0-8572-abaa8fc4cb59

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2a351 and d6dd0e9.

📒 Files selected for processing (3)
  • crates/llm-router/Cargo.toml
  • crates/llm-router/src/lib.rs
  • tests/llm_router_test.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/p3-rem-phenoAI-6
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/p3-rem-phenoAI-6

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.

@sonarqubecloud

Copy link
Copy Markdown

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jun 29, 2026
Comment on lines +130 to +144
let _response = http_request
.send()
.await
.map_err(|e| LlmError::Provider(e.to_string()))?;
.map_err(|e| {
if e.is_timeout() {
let timeout_ms = request.timeout_ms.unwrap_or(120000);
LlmError::Timeout { timeout_ms }
} else if e.is_connect() {
error!(provider = %self.provider_name(), error = %e, "connection refused by provider");
LlmError::Provider(format!("connection failed: {}", e))
} else {
error!(provider = %self.provider_name(), error = %e, "provider request failed");
LlmError::Provider(e.to_string())
}
})?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Replace this placeholder-style response handling by fully consuming and mapping the provider response into the completion output rather than discarding it. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The code explicitly binds the provider response to _response and then discards it, so the implementation does not consume or map the response into CompletionResponse. This is a genuine incomplete/placeholder-style implementation in changed code.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/llm-router/src/lib.rs
**Line:** 130:144
**Comment:**
	*Custom Rule: Replace this placeholder-style response handling by fully consuming and mapping the provider response into the completion output rather than discarding it.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +376 to +377
#[tokio::test]
async fn complete_with_retry_succeeds_on_first_try() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Add Functional Requirement traceability (for example an FR identifier comment) to the newly added tests so they comply with the repository’s FR-to-test linkage rule. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

FUNCTIONAL_REQUIREMENTS.md exists, and the newly added tests in this hunk do not reference any FR identifier in comments or names. The rule explicitly requires tests added or modified to reference a Functional Requirement when that file exists, so this is a real violation.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/llm-router/src/lib.rs
**Line:** 376:377
**Comment:**
	*Custom Rule: Add Functional Requirement traceability (for example an FR identifier comment) to the newly added tests so they comply with the repository’s FR-to-test linkage rule.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread tests/llm_router_test.rs
Comment on lines +142 to +147
let err = LlmError::RateLimited { retry_after_ms: 2000 };
assert!(format!("{}", err).contains("rate limited"));
assert!(format!("{}", err).contains("2000"));

let err = LlmError::Timeout;
assert_eq!(format!("{}", err), "timeout");
let err = LlmError::Timeout { timeout_ms: 5000 };
assert!(format!("{}", err).contains("timeout after 5000ms"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Add an explicit AgilePlus spec trace (or spec creation reference) next to this new test coverage so the change is clearly linked to a tracked work item. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The added test coverage introduces new behavior checks, but the file snippet itself does not show any AgilePlus spec reference or creation path. That matches the no-code-without-spec rule, so the suggestion is verified.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/llm_router_test.rs
**Line:** 142:147
**Comment:**
	*Custom Rule: Add an explicit AgilePlus spec trace (or spec creation reference) next to this new test coverage so the change is clearly linked to a tracked work item.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +130 to +144
let _response = http_request
.send()
.await
.map_err(|e| LlmError::Provider(e.to_string()))?;
.map_err(|e| {
if e.is_timeout() {
let timeout_ms = request.timeout_ms.unwrap_or(120000);
LlmError::Timeout { timeout_ms }
} else if e.is_connect() {
error!(provider = %self.provider_name(), error = %e, "connection refused by provider");
LlmError::Provider(format!("connection failed: {}", e))
} else {
error!(provider = %self.provider_name(), error = %e, "provider request failed");
LlmError::Provider(e.to_string())
}
})?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The provider call only checks transport failures from .send() and never validates HTTP status codes, so 4xx/5xx API responses are treated as successful completions. This causes unauthorized, invalid-request, and server-error responses to be returned as Ok(...) instead of Err(...), which breaks error handling and retry behavior. Validate the response status (for example by converting non-2xx into errors) before returning success. [api mismatch]

Severity Level: Critical 🚨
❌ LlmRouter callers mis-handle OpenAI 4xx/5xx as success.
⚠️ Retry classification never triggers on provider HTTP failures.
❌ Recovery hints not returned for authorization or quota errors.
Steps of Reproduction ✅
1. A library consumer constructs an `OpenAiProvider` and registers it on an `LlmRouter`
(provider struct and router defined in `crates/llm-router/src/lib.rs:86-105` and
`175-188`), then calls `LlmRouter::complete(&request)` to send a chat completion to
OpenAI.

2. `LlmRouter::complete` at `crates/llm-router/src/lib.rs:196-221` routes the request to
the provider and invokes `OpenAiProvider::complete(&request)` at
`crates/llm-router/src/lib.rs:108-167`.

3. Inside `OpenAiProvider::complete`, the HTTP request is built at
`crates/llm-router/src/lib.rs:113-124`, an optional per-request timeout is applied at
`126-128`, and `http_request.send().await` is executed at `130-132`, with only
transport-level errors mapped in the `map_err` closure at `133-144`.

4. When OpenAI returns a non-2xx HTTP status (e.g., 401 unauthorized or 500 server error),
`reqwest` still yields `Ok(Response)`; because the code at `130-144` does not inspect
`response.status()` or body for API errors, execution proceeds to construct a synthetic
`CompletionResponse` at `160-166`, logs `"completion succeeded"` at `152-158`, and returns
`Ok(...)` to `LlmRouter::complete`, causing callers to treat failed API responses as
successful completions.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/llm-router/src/lib.rs
**Line:** 130:144
**Comment:**
	*Api Mismatch: The provider call only checks transport failures from `.send()` and never validates HTTP status codes, so 4xx/5xx API responses are treated as successful completions. This causes unauthorized, invalid-request, and server-error responses to be returned as `Ok(...)` instead of `Err(...)`, which breaks error handling and retry behavior. Validate the response status (for example by converting non-2xx into errors) before returning success.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +230 to +242
let max_attempts: u32 = 3;
let mut last_error = None;

for attempt in 1..=max_attempts {
match self.complete(request).await {
Ok(response) => {
if attempt > 1 {
info!(attempt, "retry succeeded");
}
return Ok(response);
}
Err(e) if e.is_retryable() && attempt < max_attempts => {
let delay_ms = 100u64 * 2u64.pow(attempt - 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The retry loop is configured for only 3 total attempts, but the backoff formula/documentation expects delays of 100/200/400ms; with the current attempt < max_attempts guard, only 100ms and 200ms delays are ever used. This is an off-by-one in retry scheduling and reduces resilience versus the stated behavior. Align attempt count and delay schedule so the intended third backoff interval is actually reachable. [off-by-one]

Severity Level: Major ⚠️
⚠️ Third configured backoff interval never executes in retries.
⚠️ Transient failures retried less conservatively than documented.
⚠️ Resilience against persistent provider errors slightly reduced.
Steps of Reproduction ✅
1. The retry helper `LlmRouter::complete_with_retry` is defined at
`crates/llm-router/src/lib.rs:226-263` and documented at `224-225` as using exponential
backoff of `100ms, 200ms, 400ms` for transient errors.

2. In `complete_with_retry`, `max_attempts` is set to `3` and the loop `for attempt in
1..=max_attempts` at `233` drives up to three calls to `self.complete(request).await`, as
verified by the failing test provider in `TestProvider::complete` at
`crates/llm-router/src/lib.rs:300-307` which always returns `Err(LlmError::Provider("Mock
failure"))`.

3. Because `LlmError::Provider` is classified as retryable by `LlmError::is_retryable` at
`crates/llm-router/src/lib.rs:27-29`, errors from `TestProvider::complete` enter the
`Err(e) if e.is_retryable() && attempt < max_attempts` branch at `241`.

4. In that branch, the delay is computed as `delay_ms = 100u64 * 2u64.pow(attempt - 1)` at
`242`; with `max_attempts = 3`, this runs only for `attempt = 1` and `attempt = 2`
(yielding 100ms and 200ms), while the third attempt (`attempt = 3`) goes directly into the
`Err(e)` arm at `253-258` without a 400ms sleep, so the documented third backoff interval
is never used even when all three attempts are exhausted (e.g., the
`complete_with_retry_exhausts_after_max_attempts` test at
`crates/llm-router/src/lib.rs:394-408`).

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/llm-router/src/lib.rs
**Line:** 230:242
**Comment:**
	*Off By One: The retry loop is configured for only 3 total attempts, but the backoff formula/documentation expects delays of 100/200/400ms; with the current `attempt < max_attempts` guard, only 100ms and 200ms delays are ever used. This is an off-by-one in retry scheduling and reduces resilience versus the stated behavior. Align attempt count and delay schedule so the intended third backoff interval is actually reachable.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-ai Bot commented Jun 29, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@KooshaPari

Copy link
Copy Markdown
Owner Author

Closing duplicate — superseded by #67 (identical title, newer)

@KooshaPari KooshaPari closed this Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant