{"record":{"id":"7ea435bf527c008d","repo":"abhigyanpatwari/GitNexus","slug":"lockerr","errorCode":null,"errorMessage":"lockErr","messagePattern":"lockErr","errorType":"http","errorClass":null,"httpStatus":409,"severity":"warning","filePath":"gitnexus/src/server/api.ts","lineNumber":1236,"sourceCode":"        return;\n      }\n      let storagePath: string;\n      try {\n        storagePath = await requireDeletableStoragePath(entry);\n      } catch (err: any) {\n        if (err instanceof StorageDeletionError) {\n          res.status(400).json({ error: err.message });\n          return;\n        }\n        res.status(400).json({ error: err.message || 'Unsafe index storage path' });\n        return;\n      }\n\n      // Acquire repo lock — prevents deleting while analyze/embed is in flight\n      const lockKey = storagePath;\n      const lockErr = acquireRepoLock(lockKey);\n      if (lockErr) {\n        res.status(409).json({ error: lockErr });\n        return;\n      }\n\n      try {\n        // Close any open LadybugDB handle before deleting files\n        try {\n          await closeLbug();\n        } catch {}\n\n        // 1. Delete the .gitnexus index/storage directory\n        await fs.rm(storagePath, { recursive: true, force: true }).catch(() => {});\n\n        // 2. Delete the cloned repo dir if it lives under ~/.gitnexus/repos/.\n        // getCloneDir now throws on names that are not filesystem-safe (e.g.\n        // local repos registered with names like \"my project\" or \"org/repo\").\n        // Such repos legitimately have no clone dir, so treat the rejection as\n        // \"nothing to clean up\" rather than letting it fail the delete handler.\n        let cloneDir: string | null = null;","sourceCodeStart":1218,"sourceCodeEnd":1254,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/ac9a4e9abd8fd3058c070b72c23402a4f887929a/gitnexus/src/server/api.ts#L1218-L1254","documentation":"A 409 Conflict returned when acquireRepoLock fails during repo deletion. GitNexus holds a per-storage-path lock so a repo is not deleted while an analyze or embed job is reading/writing its index. The body's error field explains which operation holds the lock.","triggerScenarios":"DELETE /api/repos/:name while an analyze or embed job is actively running against the same repo's storage path.","commonSituations":"Typical when CI kicks off a re-index and a cleanup script deletes repos concurrently, or when a user clicks delete in the web UI while a background analyze triggered by the CLI is still running.","solutions":["Wait for the in-flight analyze/embed job to finish, then retry the delete","Poll the job status endpoint until it reports completed/failed before deleting","If a stale lock persists after a crashed job, restart the GitNexus server process to release it","Serialize operations: gate deletion behind completion of indexing jobs in your automation"],"exampleFix":"// before: delete while indexing\ncurl -X DELETE localhost:4747/api/repos/my-repo // 409 { error: lockErr }\n\n// after: wait for the job first\nconst job = await res.json();\nwhile ((await fetch(`/api/jobs/${job.id}`)).status !== 'completed') await sleep(1000);\ncurl -X DELETE localhost:4747/api/repos/my-repo // 200","handlingStrategy":"retry","validationCode":"const jobs = await (await fetch('/api/jobs?repo=' + name)).json();\nif (jobs.some(j => j.status === 'running')) throw new Error('job in flight; defer delete');","typeGuard":null,"tryCatchPattern":"for (let i = 0; i < 5; i++) {\n  const res = await fetch(`/api/repos/${name}`, { method: 'DELETE' });\n  if (res.status !== 409) return res;\n  await sleep(2000 * (i + 1));\n}\nthrow new Error('repo stayed locked; an analyze/embed job may be stuck');","preventionTips":["Poll job status endpoints before deleting or re-indexing a repo","Serialize automation so delete never overlaps analyze/embed","Restart the server to clear locks left by crashed jobs"],"tags":["http-409","lock","concurrency","conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"ac9a4e9abd8fd3058c070b72c23402a4f887929a","analyzedAt":"2026-09-15T23:29:44.066Z","contentChangedAt":"2026-09-15T23:29:44.066Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}