{"record":{"id":"1b26091149d5fbcb","repo":"coleam00/Archon","slug":"failed-to-move-extracted-web-ui-from-tmpdir-to","errorCode":null,"errorMessage":"Failed to move extracted web UI from ${tmpDir} to ${targetDir}: ${toError(err).message}","messagePattern":"Failed to move extracted web UI from (.+?) to (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/serve.ts","lineNumber":272,"sourceCode":"    }\n  } finally {\n    rmSync(tarballPath, { force: true });\n  }\n\n  // Verify extraction produced expected layout\n  if (!existsSync(`${tmpDir}/index.html`)) {\n    cleanupAndThrow(\n      tmpDir,\n      'Extraction produced unexpected layout — index.html not found in extracted dir'\n    );\n  }\n\n  // Atomic move into place\n  mkdirSync(dirname(targetDir), { recursive: true });\n  try {\n    renameSync(tmpDir, targetDir);\n  } catch (err) {\n    cleanupAndThrow(\n      tmpDir,\n      `Failed to move extracted web UI from ${tmpDir} to ${targetDir}: ${toError(err).message}`\n    );\n  }\n  // Closes the last phase: staged-archive removal, layout check, and the rename\n  // of a freshly written tree — all after-tar filesystem work.\n  log.info(\n    { targetDir, durationMs: Math.round(performance.now() - extractionEndedAt) },\n    'web_dist.installed'\n  );\n  console.log(`Extracted to ${targetDir}`);\n}\n\nfunction cleanupAndThrow(tmpDir: string, message: string): never {\n  rmSync(tmpDir, { recursive: true, force: true });\n  throw new Error(message);\n}\n","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/cli/src/commands/serve.ts#L254-L290","documentation":"downloadWebDist stages the extracted web UI in a temp dir and then atomically renames it into the target directory; if renameSync throws, cleanupAndThrow removes the temp dir and raises this error wrapping the OS message. renameSync fails across filesystems (EXDEV) or when the destination can't be created/replaced. The atomic move guarantees the server never sees a half-installed web UI.","triggerScenarios":"serveCommand -> downloadWebDist calls renameSync(tmpDir, targetDir) and it throws: temp dir and target on different filesystems (EXDEV), target path's parent not creatable (permissions), target existing as a non-empty dir/file that can't be replaced, or the target locked by a running process (EBUSY, Windows).","commonSituations":"TMPDIR on tmpfs while the install lives on the root filesystem (classic cross-device rename); read-only install prefix; leftover corrupt target dir from a previous failed install; another archon process holding the target open on Windows.","solutions":["Read the wrapped OS message: EXDEV means set TMPDIR to a directory on the same filesystem as the installation, or copy-then-rename instead of rename.","Remove/repair a leftover target directory from a prior failed install (stop the server first), then re-run serve.","Fix permissions on the installation parent directory so mkdir/rename can succeed.","On Windows, stop other processes locking the target path before retrying."],"exampleFix":"// before\nrenameSync(tmpDir, targetDir); // throws EXDEV when tmp and target are different filesystems\n// after\ntry {\n  renameSync(tmpDir, targetDir);\n} catch (err) {\n  if ((err as NodeJS.ErrnoException).code === 'EXDEV') {\n    cpSync(tmpDir, targetDir, { recursive: true });\n    rmSync(tmpDir, { recursive: true, force: true });\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"import { statSync } from 'node:fs';\nconst tmpDev = statSync(tmpDir).dev;\nconst targetDev = statSync(dirname(targetDir)).dev;\nif (tmpDev !== targetDev) {\n  throw new Error(`rename would fail (EXDEV): TMPDIR (${tmpDir}) and target (${targetDir}) are on different filesystems; set TMPDIR to ${dirname(targetDir)}`);\n}\nif (existsSync(targetDir)) {\n  throw new Error(`Target ${targetDir} already exists; stop the server and remove it before upgrading`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await serveCommand();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Failed to move extracted web UI')) {\n    if (err.message.includes('EXDEV') || err.message.includes('cross-device')) {\n      // set TMPDIR to a directory on the same filesystem as the install, retry\n    } else if (err.message.includes('EBUSY') || err.message.includes('EPERM')) {\n      // stop processes locking the target (esp. Windows), retry\n    }\n  } else throw err;\n}","preventionTips":["Set TMPDIR to the same filesystem/volume as the archon installation.","Stop the running server before upgrading the web UI so the target isn't locked.","Clean leftover target directories from failed installs before retrying.","Keep the install prefix user-writable (avoid root-owned installs for user-run servers)."],"tags":["filesystem","rename","exdev","install"],"backgroundTag":"cross-device-rename-failed","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}