abhigyanpatwari/GitNexus · error · BadRequestError
Analysis already in progress (job ${job.id})
Error message
Analysis already in progress (job ${job.id}) What it means
Inside POST /api/analyze/upload, deps.createJob (the same single-slot JobManager behind error 244) threw 'Analysis already in progress (job ...)' because a non-terminal job for a different target holds the slot. The upload handler catches it and rethrows BadRequestError(msg, 409) so HTTP callers see a correct conflict status rather than a generic 500.
Source
Thrown at gitnexus/src/server/analyze-upload.ts:111
innerIsDir = false;
}
if (!innerIsDir) {
throw new BadRequestError('Upload must be a folder');
}
const finalName = await pickAvailableName(baseName);
const finalDir = getUploadDir(finalName);
// createJob occupies the single analysis slot (throws 'already in
// progress' → 409). From here on, ANY error before launch MUST release
// the slot via failJob in the catch, or the server wedges all analyses.
let job: UploadJobRef;
try {
job = deps.createJob({ repoPath: finalDir });
} catch (err) {
const msg = err instanceof Error ? err.message : '';
if (msg.includes('already in progress')) {
throw new BadRequestError(msg, 409);
}
throw err;
}
createdJobId = job.id;
// Promote staging → persistent upload dir. Both live under UPLOAD_ROOT's
// filesystem, so this rename stays atomic (no EXDEV).
await fsp.mkdir(UPLOAD_ROOT, { recursive: true });
await fsp.rename(innerRoot, finalDir);
promotedDir = finalDir;
const oldStage = stageRoot;
stageRoot = undefined;
await fsp.rm(oldStage, { recursive: true, force: true }).catch(() => {});
// Drop any crafted index the upload may have carried (a `.gitnexus`
// segment passes containment); the worker will build a fresh one.
await fsp
.rm(path.join(finalDir, '.gitnexus'), { recursive: true, force: true })View on GitHub (pinned to aac7515d2a)
Solutions
- Poll the active job's status until terminal, then re-upload
- Cancel the in-flight job if it is no longer wanted
- Disable/queue the upload action in the UI while a job is active
Example fix
// before
const res = await fetch('/api/analyze/upload', { method: 'POST', body: formData });
if (res.status === 409) throw new Error('upload failed');
// after
if (res.status === 409) {
await waitForTerminalJob(); // poll job status
return fetch('/api/analyze/upload', { method: 'POST', body: formData });
}
return res; Defensive patterns
Strategy: retry
Validate before calling
async function slotFree(getActiveJob, isTerminal) {
const j = await getActiveJob();
return !j || isTerminal(j.status);
} Type guard
function isConflictResponse(res) { return res.status === 409; } Try / catch
async function uploadWithRetry(formData, { intervalMs = 5000, maxMs = 30 * 60 * 1000 } = {}) {
const deadline = Date.now() + maxMs;
for (;;) {
const res = await fetch('/api/analyze/upload', { method: 'POST', body: formData });
if (res.status !== 409) return res;
if (Date.now() > deadline) throw new Error('analysis slot still busy');
await sleep(intervalMs);
}
} Prevention
- Poll the active job to a terminal state before re-uploading
- Offer a cancel action in the UI when a 409 lands
- Queue uploads client-side instead of firing them concurrently
When it happens
Trigger: POST /api/analyze/upload while any other analysis — git-URL clone, local-path analyze, or a previous upload — is in a non-terminal status.
Common situations: Two browser tabs uploading folders concurrently; an upload fired while a long clone is running; UI not disabling the upload button during an active job.
Related errors
- Analysis already in progress (job ${job.id})
- Analysis already in progress (job ${job.id})
- Could not allocate an upload directory after ${MAX_NAME_COLL
- GitNexus: unable to acquire init lock after ${INIT_LOCK_MAX_
- LadybugDB unavailable for ${repoId}. Another process may be
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/944ed3c61e961212.
Report an issue: GitHub.