abhigyanpatwari/GitNexus · error

Analysis already in progress (job ${job.id})

Error message

Analysis already in progress (job ${job.id})

What it means

JobManager.createJob enforces a single analysis slot. It first dedups: an active job for the same repoUrl or repoPath is returned instead of throwing. Only when a DIFFERENT repo has a non-terminal job (queued/cloning/analyzing) does it throw this error, guarding the one-analysis-at-a-time invariant of POST /api/analyze.

Source

Thrown at gitnexus/src/server/analyze-job.ts:114

  /** Create a new job, or return existing active job for the same repo. */
  createJob(params: { repoUrl?: string; repoPath?: string }): AnalyzeJob {
    // Dedup: return existing active job for the same repo (by URL or path)
    for (const job of this.jobs.values()) {
      if (!this.isTerminal(job.status)) {
        const isSameRepo =
          (params.repoUrl && job.repoUrl === params.repoUrl) ||
          (params.repoPath && job.repoPath === params.repoPath);
        if (isSameRepo) {
          return job;
        }
      }
    }

    // Single-slot: reject if another job is active (different repo)
    for (const job of this.jobs.values()) {
      if (!this.isTerminal(job.status)) {
        throw new Error(`Analysis already in progress (job ${job.id})`);
      }
    }

    const job: AnalyzeJob = {
      id: randomUUID(),
      status: 'queued',
      repoUrl: params.repoUrl,
      repoPath: params.repoPath,
      progress: { phase: 'queued', percent: 0, message: 'Waiting to start...' },
      startedAt: Date.now(),
      retryCount: 0,
    };

    this.jobs.set(job.id, job);
    return job;
  }

  getJob(id: string): AnalyzeJob | undefined {

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Poll the existing job (GET status endpoint) until it reaches a terminal status, then retry the request
  2. Cancel the active job via the cancel endpoint if it is stale or unwanted
  3. Serialize analyze calls in your orchestration so only one is in flight
  4. If a job is wedged non-terminal with no worker alive, restart the gitnexus server to clear the in-memory map

Example fix

// before
await fetch('/api/analyze', { method: 'POST', body: JSON.stringify({ url: repoB }) }); // 500: already in progress

// after
await waitForTerminalJob(activeJobId); // poll status until succeeded/failed
await fetch('/api/analyze', { method: 'POST', body: JSON.stringify({ url: repoB }) });
Defensive patterns

Strategy: retry

Validate before calling

async function noActiveJob(getStatus) {
  const s = await getStatus(); // e.g. GET /api/analyze/status/:id or jobs listing
  return !s || ['succeeded', 'failed', 'cancelled'].includes(s.status);
}

Type guard

function isTerminalJobStatus(status) {
  return ['succeeded', 'failed', 'cancelled'].includes(status);
}

Try / catch

async function createJobWithRetry(createJob, p, { pollMs = 2000, maxMs = 30 * 60 * 1000 } = {}) {
  for (;;) {
    try { return createJob(p); }
    catch (e) {
      if (!/already in progress/.test(String(e.message))) throw e;
      const m = e.message.match(/job (\S+\))/); // active job id
      await waitForTerminal(m && m[1], { pollMs, maxMs }); // poll status until terminal
    }
  }
}

Prevention

When it happens

Trigger: POST /api/analyze with url/path for repo B while repo A's job is in any non-terminal status; the in-memory this.jobs map has no terminal job when the request arrives.

Common situations: A UI letting a user queue a second repo while the first analyzes; automation firing parallel analyze requests; a wedged worker whose job never reaches succeeded/failed, permanently holding the slot until server restart (jobs are in-memory).

Related errors


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