abhigyanpatwari/GitNexus · warning
Job already ${job.status}
Error message
Job already ${job.status} What it means
HTTP 400 returned by DELETE /api/analyze/:jobId when the job exists but is already terminal — isTerminalJobStatus covers 'complete' and 'failed' only. Terminal outcomes are immutable by design (updateJob drops later transitions so a racing complete/error cannot flip a result), so there is nothing left to cancel. A successful cancel itself sets status 'failed' with error 'Cancelled by user'.
Source
Thrown at gitnexus/src/server/api.ts:1704
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);
// DELETE /api/analyze/:jobId — cancel a running analysis job
app.delete('/api/analyze/:jobId', requireTrustedOrigin, (req, res) => {
const jobId = req.params.jobId as string;
const job = jobManager.getJob(jobId);
if (!job) {
res.status(404).json({ error: 'Job not found' });
return;
}
if (isTerminalJobStatus(job.status)) {
res.status(400).json({ error: `Job already ${job.status}` });
return;
}
jobManager.cancelJob(jobId, 'Cancelled by user');
res.json({ id: job.id, status: 'failed', error: 'Cancelled by user' });
});
// ── Embedding endpoints ────────────────────────────────────────────
const embedJobManager = new JobManager();
// POST /api/embed — trigger server-side embedding generation
app.post(
'/api/embed',
createRouteLimiter({ limit: 20 }),
requireTrustedOrigin,
async (req, res) => {
try {
const entry = await resolveRepo(requestedRepo(req));View on GitHub (pinned to 0d1aed942f)
Solutions
- Read the status out of the message ('Job already complete' / 'Job already failed') and treat the job as finished — POST /api/analyze again if you want a fresh run
- Guard before cancelling: GET /api/analyze/:jobId first and only DELETE when status is non-terminal
- Disable cancel controls in the UI once status is complete/failed
- Remember cancel = forced 'failed'; don't re-cancel an already-cancelled job
Example fix
// before
await fetch(`/api/analyze/${jobId}`, { method: 'DELETE' }); // 400 once terminal
// after
const job = await getJson(`/api/analyze/${jobId}`);
if (job.status !== 'complete' && job.status !== 'failed') {
await fetch(`/api/analyze/${jobId}`, { method: 'DELETE' });
} Defensive patterns
Strategy: validation
Validate before calling
// Only cancel live jobs
const { status } = await getJson(`/api/analyze/${jobId}`);
const live = status !== 'complete' && status !== 'failed';
if (live) await fetch(`/api/analyze/${jobId}`, { method: 'DELETE' }); Type guard
type JobStatus = 'queued' | 'cloning' | 'analyzing' | 'loading' | 'complete' | 'failed'; const isTerminalJobStatus = (s: JobStatus): boolean => s === 'complete' || s === 'failed';
Try / catch
If the DELETE still returns 400 'Job already …', accept it as the terminal verdict and optionally start a new analysis; do not retry the cancel.
Prevention
- Poll status immediately before issuing a cancel
- Map complete/failed to 'nothing to cancel' in UI state machines
- Cancelling sets failed — plan status displays accordingly
When it happens
Trigger: DELETE after the job completed; DELETE after it failed on its own; a second DELETE after the first succeeded (the job is now 'failed', so you get 400 rather than 404 while the record lives); a cancel racing the final status update.
Common situations: UI cancel buttons lagging behind completion; pollers deciding to cancel after observing the terminal state; retry wrappers that cancel-then-restart.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Job not found
- Job not found
- Analysis already in progress (job ${job.id})
- Uploaded folder has no usable name
- Upload must be a folder
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/130bd7889951f611.
Report an issue: GitHub.