{"record":{"id":"2e8df4692c4d66b9","repo":"abhigyanpatwari/GitNexus","slug":"job-not-found","errorCode":null,"errorMessage":"Job not found","messagePattern":"Job not found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"gitnexus/src/server/api.ts","lineNumber":1676,"sourceCode":"  // Securely ingests the multipart upload into a sandbox, promotes it to a\n  // persistent dir, and analyzes it via the shared job/worker machinery.\n  // localhost-only (no cross-origin write reach) + conservative rate limit.\n  app.post(\n    '/api/analyze/upload',\n    createRouteLimiter({ limit: 5 }),\n    requireTrustedOrigin,\n    createAnalyzeUploadHandler({\n      createJob: (params) => jobManager.createJob(params),\n      launch: (job, targetPath, opts) => launchAnalysisWorker(job, targetPath, opts),\n      failJob: (jobId, error) => jobManager.updateJob(jobId, { status: 'failed', error }),\n    }),\n  );\n\n  // GET /api/analyze/:jobId — poll job status\n  app.get('/api/analyze/:jobId', (req, res) => {\n    const job = jobManager.getJob(req.params.jobId);\n    if (!job) {\n      res.status(404).json({ error: 'Job not found' });\n      return;\n    }\n    res.json({\n      id: job.id,\n      status: job.status,\n      repoUrl: job.repoUrl,\n      repoPath: job.repoPath,\n      repoName: job.repoName,\n      progress: job.progress,\n      error: job.error,\n      startedAt: job.startedAt,\n      completedAt: job.completedAt,\n    });\n  });\n\n  // GET /api/analyze/:jobId/progress — SSE stream (shared helper)\n  mountSSEProgress(app, '/api/analyze/:jobId/progress', jobManager);\n","sourceCodeStart":1658,"sourceCodeEnd":1694,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/server/api.ts#L1658-L1694","documentation":"HTTP 404 returned by GET /api/analyze/:jobId when the id matches no tracked job. Job state lives in an in-memory Map inside the JobManager: records are lost when the serve process exits and are swept one hour (JOB_TTL_MS) after the job reaches a terminal state, with a cleanup sweep every 5 minutes. Ids are UUIDs minted by POST /api/analyze's 202 response.","triggerScenarios":"Polling a typo'd or truncated UUID; polling more than an hour after the job completed; polling a jobId from a previous server process (restart, crash, upgrade); ids persisted in a URL or localStorage across sessions.","commonSituations":"Dashboards that store jobIds long-term; page reloads after `gitnexus serve` was restarted; scripts resumed the next day; hand-copying ids and dropping characters.","solutions":["Treat 404 as 'job record gone', not 'analysis failed' — the finished index persists on disk independently; confirm via GET /api/repos that the repo is indexed","If the repo is missing too, re-run POST /api/analyze to get a fresh jobId","Consume jobIds promptly after the 202 and expect expiry roughly one hour after completion","Persist the repo name/URL (stable identity) rather than the jobId"],"exampleFix":"// before\nconst job = await getJson(`/api/analyze/${jobId}`); // 404 after restart/expiry\n\n// after\nconst res = await fetch(`/api/analyze/${jobId}`);\nif (res.status === 404) {\n  const repos = await getJson('/api/repos');\n  const indexed = repos.some((r: any) => r.name === repoName);\n  if (!indexed) jobId = (await postAnalyze({ url: repoUrl })).jobId; // restart only if needed\n}","handlingStrategy":"fallback","validationCode":"// Cheap pre-check: the id must be the UUID the 202 handed back\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nif (!UUID_RE.test(jobId)) throw new Error(`jobId is not a valid job UUID: ${jobId}`);","typeGuard":"const isJobId = (v: unknown): v is string =>\n  typeof v === 'string' &&\n  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);","tryCatchPattern":"On 404 from the poll endpoint, fall back to the durable outcome (GET /api/repos) and only re-trigger /api/analyze when the repo itself is missing — never loop-retry the same dead id.","preventionTips":["Store repo identity, not jobIds, for anything long-lived","Assume job records are ephemeral: in-memory, ~1h TTL after completion, gone on restart","Keep the serve process alive while a UI polls, or re-issue analysis on 404"],"tags":["http-404","job-scheduler","polling","lifecycle","ttl"],"backgroundTag":"async-job-not-found","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}