abhigyanpatwari/GitNexus · warning

Job not found

Error message

Job not found

What it means

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.

Source

Thrown at gitnexus/src/server/api.ts:1676

  // Securely ingests the multipart upload into a sandbox, promotes it to a
  // persistent dir, and analyzes it via the shared job/worker machinery.
  // localhost-only (no cross-origin write reach) + conservative rate limit.
  app.post(
    '/api/analyze/upload',
    createRouteLimiter({ limit: 5 }),
    requireTrustedOrigin,
    createAnalyzeUploadHandler({
      createJob: (params) => jobManager.createJob(params),
      launch: (job, targetPath, opts) => launchAnalysisWorker(job, targetPath, opts),
      failJob: (jobId, error) => jobManager.updateJob(jobId, { status: 'failed', error }),
    }),
  );

  // GET /api/analyze/:jobId — poll job status
  app.get('/api/analyze/:jobId', (req, res) => {
    const job = jobManager.getJob(req.params.jobId);
    if (!job) {
      res.status(404).json({ error: 'Job not found' });
      return;
    }
    res.json({
      id: job.id,
      status: job.status,
      repoUrl: job.repoUrl,
      repoPath: job.repoPath,
      repoName: job.repoName,
      progress: job.progress,
      error: job.error,
      startedAt: job.startedAt,
      completedAt: job.completedAt,
    });
  });

  // GET /api/analyze/:jobId/progress — SSE stream (shared helper)
  mountSSEProgress(app, '/api/analyze/:jobId/progress', jobManager);

View on GitHub (pinned to 0d1aed942f)

Solutions

  1. 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
  2. If the repo is missing too, re-run POST /api/analyze to get a fresh jobId
  3. Consume jobIds promptly after the 202 and expect expiry roughly one hour after completion
  4. Persist the repo name/URL (stable identity) rather than the jobId

Example fix

// before
const job = await getJson(`/api/analyze/${jobId}`); // 404 after restart/expiry

// after
const res = await fetch(`/api/analyze/${jobId}`);
if (res.status === 404) {
  const repos = await getJson('/api/repos');
  const indexed = repos.some((r: any) => r.name === repoName);
  if (!indexed) jobId = (await postAnalyze({ url: repoUrl })).jobId; // restart only if needed
}
Defensive patterns

Strategy: fallback

Validate before calling

// Cheap pre-check: the id must be the UUID the 202 handed back
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!UUID_RE.test(jobId)) throw new Error(`jobId is not a valid job UUID: ${jobId}`);

Type guard

const isJobId = (v: unknown): v is string =>
  typeof v === 'string' &&
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: Dashboards that store jobIds long-term; page reloads after `gitnexus serve` was restarted; scripts resumed the next day; hand-copying ids and dropping characters.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20). Data as JSON: /api/errors/2e8df4692c4d66b9. Report an issue: GitHub.