{"record":{"id":"28c1611506404911","repo":"abhigyanpatwari/GitNexus","slug":"url-must-be-a-string","errorCode":null,"errorMessage":"\"url\" must be a string","messagePattern":"\"url\" must be a string","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"gitnexus/src/server/api.ts","lineNumber":1506,"sourceCode":"  // POST /api/analyze — start a new analysis job\n  app.post(\n    '/api/analyze',\n    createRouteLimiter({ limit: 10 }),\n    requireTrustedOrigin,\n    async (req, res) => {\n      try {\n        const {\n          url: repoUrl,\n          path: repoLocalPath,\n          force,\n          embeddings,\n          dropEmbeddings,\n          token: repoToken,\n        } = req.body;\n\n        // Input type validation\n        if (repoUrl !== undefined && typeof repoUrl !== 'string') {\n          res.status(400).json({ error: '\"url\" must be a string' });\n          return;\n        }\n        if (repoLocalPath !== undefined && typeof repoLocalPath !== 'string') {\n          res.status(400).json({ error: '\"path\" must be a string' });\n          return;\n        }\n\n        if (!repoUrl && !repoLocalPath) {\n          res.status(400).json({ error: 'Provide \"url\" (git URL) or \"path\" (local path)' });\n          return;\n        }\n\n        // Token: optional, restricted charset to prevent header smuggling\n        // (CRLF), bound length, and bound to github.com (see validateAnalyzeToken).\n        const tokenError = validateAnalyzeToken(repoToken, repoUrl);\n        if (tokenError) {\n          res.status(tokenError.status).json({ error: tokenError.error });\n          return;","sourceCodeStart":1488,"sourceCodeEnd":1524,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/api.ts#L1488-L1524","documentation":"HTTP 400 returned by POST /api/analyze when the JSON body contains a \"url\" field whose value is not a JavaScript string (number, boolean, object, array, or null all qualify). GitNexus validates the analyze inputs at the route boundary because the URL is handed directly to the git clone machinery, which requires a string. This is a client contract violation detected before any job is created.","triggerScenarios":"POST /api/analyze with Content-Type: application/json and a body like {\"url\": 123}, {\"url\": null}, {\"url\": [\"https://github.com/org/repo\"]} or {\"url\": {\"href\": \"...\"}}. Typical producers: forgetting .toString() on a WHATWG URL object, a form/query-string serializer wrapping scalar fields in arrays, or undefined being serialized as null.","commonSituations":"A web UI passing a parsed URL object instead of its href; a client refactor switching to query-string-style bodies (?url=...) that a serializer turns into arrays; sending a numeric repository id; JSON libraries that emit explicit nulls for absent values.","solutions":["Send url as a single JSON string: {\"url\":\"https://github.com/org/repo\"} with Content-Type: application/json","Stringify URL objects before sending: use url.toString() or url.href","Omit the key instead of sending \"url\": null — undefined counts as absent, null is a type error","For local directories send \"path\" (an absolute path string) instead of \"url\""],"exampleFix":"// before\nconst body = { url: new URL('https://github.com/org/repo') }; // object, not string\nawait fetch('/api/analyze', { method: 'POST', body: JSON.stringify(body) });\n\n// after\nconst body = { url: 'https://github.com/org/repo' };\nawait fetch('/api/analyze', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(body),\n});","handlingStrategy":"validation","validationCode":"// Run before POSTing to /api/analyze\nconst assertAnalyzeBody = (b: Record<string, unknown>) => {\n  if (b.url !== undefined && typeof b.url !== 'string')\n    throw new TypeError(`\"url\" must be a string, got ${typeof b.url}`);\n  if (b.path !== undefined && typeof b.path !== 'string')\n    throw new TypeError(`\"path\" must be a string, got ${typeof b.path}`);\n};","typeGuard":"const isAnalyzeBody = (b: unknown): b is { url?: string; path?: string } => {\n  if (typeof b !== 'object' || b === null) return false;\n  const o = b as Record<string, unknown>;\n  return (\n    (o.url === undefined || typeof o.url === 'string') &&\n    (o.path === undefined || typeof o.path === 'string')\n  );\n};","tryCatchPattern":"On a 400 response read { error } from the JSON body and fix the payload — this error is permanent for that request, so never blind-retry the identical body.","preventionTips":["Always set Content-Type: application/json on /api/analyze requests","Use url.href for URL objects; omit optional keys rather than sending null","Route request bodies through isAnalyzeBody in client code and tests"],"tags":["http-400","validation","request-body","express","api"],"backgroundTag":"request-body-validation","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}