Repository navigation
fix(api): answer bad requests to /api/grade with 4xx, not 500 - #31
Merged
Merged
Conversation
`/api/grade` threw `Error("MissingURL")` into its generic catch, which
logged it with `console.error` and returned 500. Search crawlers hit this
constantly: the resume-checker dropzone is a real
`<form action="/api/grade">` carrying a honeypot field named `name`, and
crawlers ignore `method="POST"` and issue a GET with the field names — the
`?name=` shape visible in the log drain. Every one of those was a 500 in
the error logs and a broken endpoint as far as Bing was concerned.
Validate method and parameters before the try block so client mistakes
stay 4xx, and stop returning `e.message` on the 500 path — the resume
checker renders that string straight into a badge, so whatever the PDF
parser or the model threw was being shown to the user. Same reasoning as
c5179a0 for the take-home checker.
GET ?name=2026 500 -> 400 MissingURL
GET (no params) 500 -> 400 MissingURL
GET ?url= 500 -> 400 MissingURL
PUT 404 -> 405 MethodNotAllowed
POST non-multipart -> 400 InvalidUploadRequest
internal failure 500 raw message -> 500 GradingError
`InvalidPDFException` still answers 400 and the example-resume shortcut is
untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143SXEp4gahUMp68Dshbc6y
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while diagnosing production error-log noise from the Vercel drain.
Problem
Every malformed request to
/api/gradewas logged as a server error and answered with a 500:Still reproducible on production right now:
Two causes:
src/pages/api/grade.tsthrewError("MissingURL")into its generic catch, whichconsole.errors and returns 500. A client mistake reported as a server fault.src/resume-checker/pages/index.tsx— the dropzone is a real<form action="/api/grade">with a honeypot fieldname="name". Crawlers ignoremethod="POST"and issue a GET with the field names attached, which is exactly the?name=shape in the drain.robots.ts(merged in fix(seo): sitemap, robots, canonicals and per-page titles/descriptions #28) stops the crawl traffic; this fixes the response itself.Also on the 500 path,
res.send({ error: e.message })returned the raw error. The resume checker renders that string directly into a red badge (error-badge.tsxprintserror.messagewith no mapping), so whateverpdf-parseor the model threw was being shown to end users. Same reasoning as c5179a0 for the take-home checker.Change
Validate method and parameters before the try block:
GET ?name=2026MissingURLGETno paramsMissingURLGET ?url=(empty)MissingURLPUTMethodNotAllowedPOSTnon-multipartInvalidUploadRequestGradingErrorInvalidPDFExceptionstill answers 400, and the example-resume shortcut is untouched.Test plan
tsc,next lint,next buildpassnext start?url=public/s_resume.pdfstill returns 200 with the example gradeInvalidPDFExceptionGradingErrorinstead of leakingfetch failedinternalsSupersedes #12
#12 fixes the same status codes but has gone stale: it pins
google("gemini-2.5-flash")via@ai-sdk/googlewithmode: "json", which would revert the AI Gateway routing merged in #18 for SIL-3430. This PR takes the same early-validation shape on top of currentmainwithout that regression. Suggest closing #12 in favour of this, or cherry-picking its contract tests on top.Not addressed here
Still open in the same file, deliberately out of scope:
fetch(url)atgrade.tstakes an arbitrary caller-supplied URL with no scheme/host allowlist, no timeout and no response-size cap, on a route withmaxDuration: 300. The client-sidehttps/.pdfguard is bypassed by calling the API directly.bodyParser: falseplusBuffer.concatover the whole stream, no size limit.🤖 Generated with Claude Code
https://claude.ai/code/session_0143SXEp4gahUMp68Dshbc6y