{"record":{"id":"b75d5db5bea2d409","repo":"janhq/jan","slug":"failed-to-normalize-backend-layout-string-e","errorCode":null,"errorMessage":"Failed to normalize backend layout: ${String(e)}","messagePattern":"Failed to normalize backend layout: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/llamacpp-extension/src/index.ts","lineNumber":2925,"sourceCode":"      }\n      if (foundDir !== expectedBinDir) {\n        const staging = `${backendDir}.staging`\n        try {\n          // Move the binary's dir into build/bin in one rename to keep relative\n          // symlinks intact (libggml.so → .so.0 → .so.0.10.0). A flat-root\n          // archive can't rename into its own subtree, so stage to a sibling.\n          if (foundDir === backendDir) {\n            await fs.mv(backendDir, staging)\n            await fs.mkdir(await joinPath([backendDir, 'build']))\n            await fs.mv(staging, expectedBinDir)\n          } else {\n            await fs.mkdir(await joinPath([backendDir, 'build']))\n            await fs.mv(foundDir, expectedBinDir)\n          }\n        } catch (e) {\n          if (await fs.existsSync(staging)) await fs.rm(staging)\n          if (await fs.existsSync(backendDir)) await fs.rm(backendDir)\n          throw new Error(`Failed to normalize backend layout: ${String(e)}`)\n        }\n      }\n    }\n\n    if (!(await fs.existsSync(expectedBinPath))) {\n      await fs.rm(backendDir)\n      throw new Error(\n        'Not a supported backend archive! Missing llama-server binary.'\n      )\n    }\n\n    try {\n      await this.refreshBackendOptions()\n      logger.info(\n        `Backend ${backendIdentifier}/${version} installed and UI refreshed`\n      )\n    } catch (e) {\n      logger.error('Backend installed but failed to refresh UI', e)","sourceCodeStart":2907,"sourceCodeEnd":2943,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/extensions/llamacpp-extension/src/index.ts#L2907-L2943","documentation":"During the layout-normalization step — when the llama-server binary was found in a non-standard location and must be moved into build/bin/ — a file move operation fails. The staging directory and backend directory are cleaned up and the error is re-thrown. The normalization preserves relative symlinks (libggml.so chain) by using a single rename.","triggerScenarios":"fs.mv fails during the staging dance: cross-device move without copy support, permission denied, another process holds a lock on the files, disk full during the move, or a stale staging directory from a previous failed attempt.","commonSituations":"Backend directory and staging are on different mount points; antivirus scanning locks files during the move; leftover .staging directory from a prior crashed install causes a conflict; disk quota exceeded.","solutions":["Retry the installation — a transient lock or race may clear.","Ensure the Jan data folder is on a single mount point with adequate free space.","Manually remove any leftover *.staging directories from a previous failed attempt.","Check for antivirus or backup software locking the backend directory during the move."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import fs from 'node:fs'\n\nfunction isStagingClear(backendDir: string): boolean {\n  return !fs.existsSync(`${backendDir}.staging`)\n}\n\n// Before install:\nif (!isStagingClear(backendDir)) {\n  fs.rmSync(`${backendDir}.staging`, { recursive: true, force: true })\n}","typeGuard":null,"tryCatchPattern":"try {\n  await extension.installBackend(path)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('normalize backend layout')) {\n    // Clean up leftover staging and retry\n    fs.rmSync(`${backendDir}.staging`, { recursive: true, force: true })\n    fs.rmSync(backendDir, { recursive: true, force: true })\n    await extension.installBackend(path)\n  } else throw e\n}","preventionTips":["Ensure the Jan data folder resides on a single filesystem (not split across mounts).","Clean up leftover .staging directories before retrying a failed install.","Disable antivirus real-time scanning on the backend directory during installation."],"tags":["backend","filesystem","normalization","symlink","move","typescript"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}