Skip to content

feat: implement AI-powered resume scoring to replace hardcoded endpoint#4377

Open
revanthbaspally6-cell wants to merge 1 commit into
anurag3407:mainfrom
revanthbaspally6-cell:feature/ai-powered-resume-scoring-4365
Open

feat: implement AI-powered resume scoring to replace hardcoded endpoint#4377
revanthbaspally6-cell wants to merge 1 commit into
anurag3407:mainfrom
revanthbaspally6-cell:feature/ai-powered-resume-scoring-4365

Conversation

@revanthbaspally6-cell

@revanthbaspally6-cell revanthbaspally6-cell commented Jul 3, 2026

Copy link
Copy Markdown

User description

  • Created resumeService.js with analyzeResume function
  • AI provider integration using getDefaultProvider()
  • Strict JSON schema enforcement for AI responses
  • Comprehensive error handling with graceful degradation
  • Updated POST /api/resume/score endpoint to use AI service
  • Replaced hardcoded mock data with real AI-generated analysis
  • Added validation for required JSON structure

Fixes #4365

Description

Brief description of changes

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Performance improvement
  • Other (describe)

Related Issue

Fixes #(issue number)

Testing

  • Unit tests pass
  • Tested Locally
  • New tests added

Screenshots (MANDATORY for UI/UX changes)

  • Attached Screenshots or Screen Recordings (showing before and after)
  • If your PR does not make any visual/UI changes, please write "N/A"

Checklist

  • Code follows project style guidelines
  • Self-reviewed my code
  • Added comments where necessary
  • Updated documentation
  • No new warnings generated

CodeAnt-AI Description

Use AI-generated resume scores instead of fixed sample results

What Changed

  • Resume scoring now returns an AI-generated evaluation instead of the previous hardcoded scores and suggestions
  • The response keeps the same sections and includes overall score, section scores, feedback, and top suggestions
  • Invalid or missing resume text is rejected with a clear error
  • If the AI returns unusable output, the request now fails with a clear analysis error instead of silently returning fake data

Impact

✅ Real resume feedback
✅ More relevant score results
✅ Clearer resume analysis errors

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

Summary by CodeRabbit

  • New Features
    • Resume scoring now uses live analysis instead of fixed placeholder results.
    • Added more detailed resume feedback, including section scores and improvement suggestions.
  • Bug Fixes
    • Improved handling of invalid or missing resume text during scoring.
    • Better error handling for malformed analysis responses, helping prevent broken score results.

- Created resumeService.js with analyzeResume function
- AI provider integration using getDefaultProvider()
- Strict JSON schema enforcement for AI responses
- Comprehensive error handling with graceful degradation
- Updated POST /api/resume/score endpoint to use AI service
- Replaced hardcoded mock data with real AI-generated analysis
- Added validation for required JSON structure

Fixes anurag3407#4365
@codeant-ai

codeant-ai Bot commented Jul 3, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

@revanthbaspally6-cell is attempting to deploy a commit to the Anurag Mishra's projects Team on Vercel.

A member of the Team first needs to authorize it.

@codeant-ai

codeant-ai Bot commented Jul 3, 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

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Replaces the hardcoded response of the /score resume endpoint with a new analyzeResume service. The service validates input, prompts an AI provider for JSON-formatted resume analysis, parses and validates the returned schema, and maps failures to ApiError responses.

Changes

Resume scoring service integration

Layer / File(s) Summary
AI resume analysis service implementation
backend/src/services/resumeService.js
New analyzeResume(resumeText) validates input, builds an AI prompt, calls the default provider, strips Markdown fences, parses/validates the JSON schema (overallScore, sections, topSuggestions), and maps errors to ApiError.
Route wiring to service
backend/src/routes/resume.js
Imports analyzeResume and replaces the static mock payload in the /score endpoint with a dynamic call, returning the result under data.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ScoreRoute
  participant analyzeResume
  participant AIProvider

  Client->>ScoreRoute: POST /api/resume/score (resumeText)
  ScoreRoute->>analyzeResume: analyzeResume(resumeText)
  analyzeResume->>analyzeResume: validate resumeText not empty
  analyzeResume->>AIProvider: generateContent(prompt)
  AIProvider-->>analyzeResume: raw JSON (possibly fenced)
  analyzeResume->>analyzeResume: strip fences, parse, validate schema
  alt valid result
    analyzeResume-->>ScoreRoute: analysisResult
    ScoreRoute-->>Client: 200 { data: analysisResult }
  else invalid JSON or missing fields
    analyzeResume-->>ScoreRoute: throw ApiError(500)
    ScoreRoute-->>Client: error response
  end
Loading

Possibly related PRs

  • anurag3407/career-pilot#1299: Implements the actual AI-powered analyzeResume service logic that was scaffolded with hardcoded mock data, replacing the fixed response in the /score endpoint with dynamic service calls and validation.
  • anurag3407/career-pilot#4239: Changes the resume-score flow to handle AI-returned JSON parsing/validation failures instead of returning mocked or fallback data.

Suggested labels: level:advanced, type:feature

Suggested reviewers: anurag3407

🚥 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 clearly states the main change: replacing the hardcoded resume scoring endpoint with AI-powered scoring.
Linked Issues check ✅ Passed The PR adds the AI resume service, updates the route, enforces JSON validation, and adds error handling as requested.
Out of Scope Changes check ✅ Passed The changes stay focused on resume scoring backend work and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
backend/src/routes/resume.js (1)

579-595: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Protect /score with auth and the AI limiter
backend/src/routes/resume.js:579-595 This is the only resume AI route that skips verifyToken and aiRateLimiter. The app-wide /api/ limiter helps, but it’s much looser than the AI-specific limit used elsewhere, so this still leaves a public billed endpoint open to abuse. Add verifyToken and aiRateLimiter, or document why /score must stay public.

🤖 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 `@backend/src/routes/resume.js` around lines 579 - 595, The /score resume AI
endpoint is missing the same protection used by the other AI routes. Update the
router.post('/score') handler in resume.js to run through verifyToken and
aiRateLimiter before analyzeResume, matching the existing protected resume AI
endpoints. If this route must remain public, document the exception clearly in
the route setup and keep the access policy consistent with the other handlers.
🧹 Nitpick comments (2)
backend/src/services/resumeService.js (2)

72-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Manual field-by-field validation is verbose and only partially strict.

topSuggestions array elements and section feedback fields aren't type-checked (only score is), so malformed sub-fields can still pass through to the client. Consider using a schema validation library (e.g., ajv or zod) to validate the full response shape in one place — this would also make it easier to keep the validation contract in sync with the prompt's schema description.

🤖 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 `@backend/src/services/resumeService.js` around lines 72 - 90, The manual
response validation in resumeService.js is only checking a few fields and leaves
nested data unchecked. Update the validation in the resume analysis flow around
the existing overallScore/sections/topSuggestions checks to use a schema-based
validator such as zod or ajv so the full analysisData shape is validated in one
place. Make sure each required section in sections validates both score and
feedback, and that topSuggestions validates the type of each array element
rather than only the array itself.

63-70: 🎯 Functional Correctness | 🔵 Trivial | ⚖️ Poor tradeoff

Fence-stripping is fragile if the model adds prose around the JSON.

The code only strips leading/trailing ``` fences; if the model prepends/appends any explanatory text (common even with "Return ONLY JSON" instructions), JSON.parse will fail and the whole request degrades to a generic 500. Consider extracting the substring between the first `{` and the last `}` as a fallback, or — more robustly — use the provider's native structured-output/JSON-schema mode if supported, which guarantees schema-conformant output instead of relying on prompt instructions and regex post-processing.

♻️ Example fallback extraction
     let jsonText = result.text.trim();
     if (jsonText.startsWith('```')) {
       jsonText = jsonText.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '').trim();
     }
+    if (!jsonText.startsWith('{')) {
+      const start = jsonText.indexOf('{');
+      const end = jsonText.lastIndexOf('}');
+      if (start !== -1 && end !== -1) jsonText = jsonText.slice(start, end + 1);
+    }

Several providers (OpenAI, OpenRouter, and others) support a response_format/json_schema structured-output mode that guarantees schema-conformant JSON rather than relying on prompt text, which would eliminate most of this manual parsing/validation entirely.

🤖 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 `@backend/src/services/resumeService.js` around lines 63 - 70, The JSON parsing
in resumeService’s response handling is too brittle because it only removes code
fences before calling JSON.parse. Update the parsing flow around the
result.text/jsonText logic to first try structured-output support if the
provider/client can return JSON schema or response_format output, and otherwise
add a fallback that extracts the substring between the first { and last } before
parsing. Keep the existing fence-stripping as a pre-step, but ensure any
surrounding prose from the model does not cause a generic failure in the
JSON.parse path.
🤖 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 `@backend/src/services/resumeService.js`:
- Around line 73-75: The validation in resumeService’s analysisData.overallScore
check is rejecting a valid score of 0 because it uses a falsy test; update the
guard to only reject null/undefined or non-number values, while still allowing 0
through. Keep the fix scoped to the overallScore validation in the resumeService
logic that throws the “Invalid response” error, so a legitimate AI result in the
0–100 range no longer falls back to a generic 500.
- Around line 15-61: The `provider.generateContent()` call in `resumeService` is
awaited directly, so a slow upstream can hang the request and consume resources.
Wrap the `generateContent` call in a timeout or abortable promise guard inside
the resume analysis flow, and make sure the timeout path returns a controlled
error before any response parsing continues.

---

Outside diff comments:
In `@backend/src/routes/resume.js`:
- Around line 579-595: The /score resume AI endpoint is missing the same
protection used by the other AI routes. Update the router.post('/score') handler
in resume.js to run through verifyToken and aiRateLimiter before analyzeResume,
matching the existing protected resume AI endpoints. If this route must remain
public, document the exception clearly in the route setup and keep the access
policy consistent with the other handlers.

---

Nitpick comments:
In `@backend/src/services/resumeService.js`:
- Around line 72-90: The manual response validation in resumeService.js is only
checking a few fields and leaves nested data unchecked. Update the validation in
the resume analysis flow around the existing
overallScore/sections/topSuggestions checks to use a schema-based validator such
as zod or ajv so the full analysisData shape is validated in one place. Make
sure each required section in sections validates both score and feedback, and
that topSuggestions validates the type of each array element rather than only
the array itself.
- Around line 63-70: The JSON parsing in resumeService’s response handling is
too brittle because it only removes code fences before calling JSON.parse.
Update the parsing flow around the result.text/jsonText logic to first try
structured-output support if the provider/client can return JSON schema or
response_format output, and otherwise add a fallback that extracts the substring
between the first { and last } before parsing. Keep the existing fence-stripping
as a pre-step, but ensure any surrounding prose from the model does not cause a
generic failure in the JSON.parse path.
🪄 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: aa02f528-01c6-42df-b6ed-1078538646d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0b4f739 and c0c7ae6.

📒 Files selected for processing (2)
  • backend/src/routes/resume.js
  • backend/src/services/resumeService.js

Comment on lines +15 to +61
const provider = getDefaultProvider();

const prompt = `You are an expert resume analyst. Analyze the following resume and provide a detailed evaluation.

Return ONLY a valid JSON object with this exact structure:
{
"overallScore": number (0-100),
"sections": {
"summary": {
"score": number (0-100),
"feedback": "string (brief feedback)"
},
"skills": {
"score": number (0-100),
"feedback": "string (brief feedback)"
},
"experience": {
"score": number (0-100),
"feedback": "string (brief feedback)"
},
"education": {
"score": number (0-100),
"feedback": "string (brief feedback)"
},
"projects": {
"score": number (0-100),
"feedback": "string (brief feedback)"
}
},
"topSuggestions": [
"string (actionable suggestion)",
"string (actionable suggestion)",
"string (actionable suggestion)"
]
}

Scoring criteria:
- Overall score should reflect the resume's overall quality
- Each section should be scored independently (0-100)
- Feedback should be specific and actionable
- Top suggestions should be the most important improvements needed
- Be honest but constructive in your assessment

Resume text:
${resumeText}`;

const result = await provider.generateContent(prompt);

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect generateContent implementations for timeout handling
ast-grep run --pattern 'generateContent($$$) { $$$ }' --lang javascript backend/src/config/aiProviders.js
rg -n -A10 'generateContent' backend/src/config/aiProviders.js

Repository: anurag3407/career-pilot

Length of output: 4200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== backend/src/config/aiProviders.js (relevant sections) =="
nl -ba backend/src/config/aiProviders.js | sed -n '1,230p'

echo
echo "== timeout / abort / race search across backend/src =="
rg -n "Promise\.race|AbortController|timeout|timed out|setTimeout\(" backend/src || true

echo
echo "== resume service call site =="
nl -ba backend/src/services/resumeService.js | sed -n '1,120p'

Repository: anurag3407/career-pilot

Length of output: 261


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== backend/src/config/aiProviders.js =="
sed -n '1,240p' backend/src/config/aiProviders.js

echo
echo "== timeout/abort/race references in backend/src =="
rg -n "Promise\.race|AbortController|setTimeout\(|timeout|timed out" backend/src || true

echo
echo "== backend/src/services/resumeService.js =="
sed -n '1,140p' backend/src/services/resumeService.js

Repository: anurag3407/career-pilot

Length of output: 18960


Add a timeout around provider.generateContent()

backend/src/services/resumeService.js awaits the AI call directly, so a slow or unresponsive upstream can tie up this unauthenticated request and exhaust connections under load. Wrap it with a timeout/abort guard before parsing the response.

🤖 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 `@backend/src/services/resumeService.js` around lines 15 - 61, The
`provider.generateContent()` call in `resumeService` is awaited directly, so a
slow upstream can hang the request and consume resources. Wrap the
`generateContent` call in a timeout or abortable promise guard inside the resume
analysis flow, and make sure the timeout path returns a controlled error before
any response parsing continues.

Comment on lines +73 to +75
if (!analysisData.overallScore || typeof analysisData.overallScore !== 'number') {
throw new Error('Invalid response: missing or invalid overallScore');
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Falsy check rejects a legitimate overallScore of 0.

!analysisData.overallScore treats 0 as invalid even though it's within the documented 0-100 range, forcing a valid AI response to fail validation and fall back to a generic 500 error.

🐛 Proposed fix
-    if (!analysisData.overallScore || typeof analysisData.overallScore !== 'number') {
+    if (typeof analysisData.overallScore !== 'number' || Number.isNaN(analysisData.overallScore)) {
       throw new Error('Invalid response: missing or invalid overallScore');
     }
📝 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 (!analysisData.overallScore || typeof analysisData.overallScore !== 'number') {
throw new Error('Invalid response: missing or invalid overallScore');
}
if (typeof analysisData.overallScore !== 'number' || Number.isNaN(analysisData.overallScore)) {
throw new Error('Invalid response: missing or invalid overallScore');
}
🤖 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 `@backend/src/services/resumeService.js` around lines 73 - 75, The validation
in resumeService’s analysisData.overallScore check is rejecting a valid score of
0 because it uses a falsy test; update the guard to only reject null/undefined
or non-number values, while still allowing 0 through. Keep the fix scoped to the
overallScore validation in the resumeService logic that throws the “Invalid
response” error, so a legitimate AI result in the 0–100 range no longer falls
back to a generic 500.

});
}

const analysisResult = await analyzeResume(resumeText);

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: This line enables a paid AI call in an endpoint that is not protected by authentication or AI rate limiting, unlike other AI routes. That allows anonymous abuse and uncontrolled provider-cost consumption. Add the same auth/rate-limit middleware chain used by other AI endpoints before invoking the analysis. [security]

Severity Level: Critical 🚨
❌ /api/resumes/score allows anonymous AI-powered resume scoring.
❌ AI provider billed for unlimited anonymous resume scoring.
⚠️ No aiRateLimiter protects resume scoring request volume.
Steps of Reproduction ✅
1. The main Express app mounts the resume router at `/api/resumes` in
`backend/src/index.js:282-284` (`app.use('/api/resumes', resumeRoutes);`), exposing all
routes in `backend/src/routes/resume.js` publicly unless protected per-route.

2. In `backend/src/routes/resume.js:579-586`, the `/score` endpoint is defined as
`router.post('/score', asyncHandler(async (req, res) => { ... }))` without `verifyToken`,
`extractAIProvider`, or `aiRateLimiter` middleware in its chain, unlike other AI-heavy
routes.

3. Inside that handler, line `589 const analysisResult = await analyzeResume(resumeText);`
calls `analyzeResume()` from `backend/src/services/resumeService.js:9-92`, which in turn
obtains a provider via `getDefaultProvider()` (line 15) and invokes
`provider.generateContent(prompt)` (line 61), making an external, billable AI request on
every call.

4. Compare this with other AI endpoints: `backend/src/routes/enhance.js:8-15` and `:49-55`
wrap AI calls with `verifyToken`, `extractAIProvider`, and `aiRateLimiter`, and
`backend/src/routes/roast.js:15-21` similarly protects `POST /api/roast`. Project
visualizer chat endpoints in `backend/src/routes/projectVisualizer.route.js:194-199,
210-217, 234-241, 253-259, 271-279, 287-290` all use `verifyToken` and `aiRateLimiter`.
The resume scoring route lacks both authentication and `aiRateLimiter`, so repeated
anonymous `POST /api/resumes/score` requests (e.g. via curl or a script) will hit
`provider.generateContent` with no auth or AI-specific rate limiting, allowing
uncontrolled external AI cost consumption.

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:** backend/src/routes/resume.js
**Line:** 589:589
**Comment:**
	*Security: This line enables a paid AI call in an endpoint that is not protected by authentication or AI rate limiting, unlike other AI routes. That allows anonymous abuse and uncontrolled provider-cost consumption. Add the same auth/rate-limit middleware chain used by other AI endpoints before invoking the analysis.

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 +10 to +12
if (!resumeText || !resumeText.trim()) {
throw new ApiError(400, 'Resume text is required');
}

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 input guard assumes the value is a string and calls .trim() directly. If a non-string value is passed, this throws a TypeError before structured validation, causing an unintended 500 response instead of a 400 validation error. Validate type before calling string methods. [type error]

Severity Level: Major ⚠️
⚠️ Non-string payloads crash /api/resumes/score handler.
⚠️ Clients receive 500 instead of proper 400 validation error.
Steps of Reproduction ✅
1. The resume scoring API is mounted at `/api/resumes` in `backend/src/index.js:284` and
defines `POST /api/resumes/score` in `backend/src/routes/resume.js:579-590`. This handler
reads `const { resumeText } = req.body;` (line 580) from arbitrary JSON input, since no
`validate()` schema middleware is applied to this route.

2. Immediately after, the route performs `if (!resumeText || !resumeText.trim()) { ... }`
at lines 582-586 in `backend/src/routes/resume.js`. If a client sends a non-string value,
e.g. `{ "resumeText": 123 }`, `!resumeText` is `false`, but `resumeText.trim` is
`undefined` and calling `resumeText.trim()` throws a `TypeError` before any structured
validation or service call.

3. The route is wrapped with `asyncHandler` (line 579), which forwards the thrown
`TypeError` to the global `errorHandler` defined in
`backend/src/middleware/errorHandler.js:12-92`. Because the error is neither an `ApiError`
nor a known Firebase/validation error, it falls through to the default branch at lines
82-87, returning HTTP 500 with an unexpected error message instead of a 400 validation
error.

4. Even if the route-level guard were fixed, `analyzeResume()` in
`backend/src/services/resumeService.js:9-12` repeats the same unchecked pattern `if
(!resumeText || !resumeText.trim()) { throw new ApiError(400, 'Resume text is required');
}`, so any other direct caller passing a non-string would again trigger a `TypeError`
rather than the intended 400 `ApiError` response, confirming the issue in the service
itself.

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:** backend/src/services/resumeService.js
**Line:** 10:12
**Comment:**
	*Type Error: The input guard assumes the value is a string and calls `.trim()` directly. If a non-string value is passed, this throws a TypeError before structured validation, causing an unintended 500 response instead of a 400 validation error. Validate type before calling string methods.

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
👍 | 👎

}

try {
const provider = getDefaultProvider();

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: This always forces the global default provider and ignores per-request AI provider/key selection used elsewhere via middleware, so BYOK/request-specific configuration cannot work for this flow. Accept an injected provider (or request context) instead of hardwiring default provider resolution in the service. [api mismatch]

Severity Level: Major ⚠️
⚠️ /api/resumes/score ignores X-AI-* BYOK provider headers.
⚠️ Resume scoring always uses server default AI provider.
Steps of Reproduction ✅
1. The project centralizes AI provider selection in
`backend/src/middleware/aiKey.js:18-69`, where `extractAIProvider` reads `X-AI-Provider`,
`X-AI-Key`, and related headers, attaches a provider instance to `req.aiProvider`, and
records `req.aiProviderSource` (e.g. user-supplied key vs server env).

2. All resume enhancement-related routes in `backend/src/routes/enhance.js:8-15, 49-55,
81-88, 111-118, 139-147, 167-175, 195-203` use the middleware chain `verifyToken,
extractAIProvider, aiRateLimiter` and pass `req.aiProvider` into the langchain helpers
(`enhanceResume`, `generateSummary`, `analyzeATSScore`, etc.), which in turn call
`resolveProvider(aiProvider)` in `backend/src/config/langchain.js:12`, choosing the
per-request provider when present and falling back to `getDefaultProvider()` otherwise.

3. By contrast, the new resume scoring flow uses `analyzeResume(resumeText)` from
`backend/src/services/resumeService.js:9-92`, and the route `POST /api/resumes/score` at
`backend/src/routes/resume.js:579-590` does not include `extractAIProvider` or any
ai-provider parameter; it simply calls the service with the text.

4. Inside `analyzeResume()`, line 15 hardwires `const provider = getDefaultProvider();`,
and line 61 calls `provider.generateContent(prompt)` using that global instance. As a
result, even if a client sends `X-AI-Provider` or `X-AI-Key` headers (which are honored on
`/api/enhance` and `/api/roast`), the `/api/resumes/score` flow cannot use them: it never
consults `req.aiProvider`, and the service ignores any injected provider, breaking the
established per-request AI configuration pattern for this feature.

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:** backend/src/services/resumeService.js
**Line:** 15:15
**Comment:**
	*Api Mismatch: This always forces the global default provider and ignores per-request AI provider/key selection used elsewhere via middleware, so BYOK/request-specific configuration cannot work for this flow. Accept an injected provider (or request context) instead of hardwiring default provider resolution in the service.

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 +73 to +75
if (!analysisData.overallScore || typeof analysisData.overallScore !== 'number') {
throw new Error('Invalid response: missing or invalid overallScore');
}

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 numeric validation incorrectly treats zero as missing. Because the condition uses a falsy check before the type check, a valid score of 0 will always be rejected and converted into a 500 error. Check for number type first and then validate numeric range explicitly. [falsy zero check]

Severity Level: Major ⚠️
⚠️ /api/resumes/score fails on extremely weak resumes.
⚠️ Users receive 500 instead of clear scoring response.
Steps of Reproduction ✅
1. The resume scoring endpoint `POST /api/resumes/score` in
`backend/src/routes/resume.js:579-590` reads `resumeText` from the request body and calls
`const analysisResult = await analyzeResume(resumeText);` (line 589).

2. `analyzeResume()` in `backend/src/services/resumeService.js:9-92` constructs a prompt
(lines 17-59) that explicitly instructs the AI to return `"overallScore": number (0-100)`
(lines 20-22), meaning 0 is a valid score for a very weak resume.

3. The AI response is parsed into `analysisData` at line 70: `const analysisData =
JSON.parse(jsonText);`. Immediately after, validation at lines 73-75 uses `if
(!analysisData.overallScore || typeof analysisData.overallScore !== 'number') { throw new
Error('Invalid response: missing or invalid overallScore'); }`.

4. When the AI legitimately returns `overallScore: 0`, `!analysisData.overallScore`
evaluates to `true` (because `!0` is `true`), so the condition is satisfied regardless of
the correct `typeof` check. This throws a generic `Error`, which is caught by the `catch`
block at lines 93-107 and, since it is neither a `SyntaxError` nor an `ApiError`, is
converted into `new ApiError(500, 'Failed to analyze resume')`. The `/api/resumes/score`
endpoint then returns HTTP 500 for valid AI output representing a zero score.

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:** backend/src/services/resumeService.js
**Line:** 73:75
**Comment:**
	*Falsy Zero Check: The numeric validation incorrectly treats zero as missing. Because the condition uses a falsy check before the type check, a valid score of 0 will always be rejected and converted into a 500 error. Check for number type first and then validate numeric range explicitly.

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 Jul 3, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

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.

[Feature]: Implement AI-Powered Resume Scoring (Replace Hardcoded Endpoint)

1 participant