{"record":{"id":"46f99349f61dc429","repo":"tinyhumansai/openhuman","slug":"expected-one-top-level-folder-under-found-n","errorCode":null,"errorMessage":"expected one top-level folder under {}, found {n}: {:?}","messagePattern":"expected one top-level folder under (.+?), found (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/runtime/node/extractor.rs","lineNumber":153,"sourceCode":"        .collect::<Result<Vec<_>, _>>()\n        .with_context(|| format!(\"reading entries of {}\", extract_root.display()))?;\n\n    // Stable order for deterministic logging.\n    entries.sort_by_key(|e| e.file_name());\n\n    let mut dirs: Vec<PathBuf> = entries\n        .into_iter()\n        .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))\n        .map(|e| e.path())\n        .collect();\n\n    match dirs.len() {\n        1 => Ok(dirs.pop().unwrap()),\n        0 => Err(anyhow!(\n            \"expected one top-level folder under {}, found none\",\n            extract_root.display()\n        )),\n        n => Err(anyhow!(\n            \"expected one top-level folder under {}, found {n}: {:?}\",\n            extract_root.display(),\n            dirs\n        )),\n    }\n}\n\n/// Atomically move `staged` into place at `final_dest`.\n///\n/// Strategy:\n/// 1. If `final_dest` already exists, move it to a sibling `.old-<pid>`\n///    path so we never lose a working install even if a later step fails.\n/// 2. Rename `staged` -> `final_dest`. On the same filesystem this is a\n///    single `rename(2)` and is atomic from the reader's perspective.\n/// 3. Best-effort cleanup of the `.old-*` directory.\n///\n/// Returns the `final_dest` path on success.\npub async fn atomic_install(staged: &Path, final_dest: &Path) -> Result<PathBuf> {","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/runtime/node/extractor.rs#L135-L171","documentation":"A structural validation guard in the Node.js archive extractor: the extraction root contained more than one top-level directory (the count and directory list are interpolated into the message). Official Node tarballs contain exactly one `node-vX.Y.Z-<platform>` folder; multiple candidates mean the archive layout is unexpected or the extraction root was reused and accumulated stale entries. Rather than picking arbitrarily, the extractor fails and lists what it found.","triggerScenarios":"Thrown at src/openhuman/runtime/node/extractor.rs:153 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Remove the extraction directory so it starts clean and re-extract","Inspect the listed directories — stale artifacts from a prior extraction sharing the root are the usual cause","If the archive itself contains multiple roots, the download may be wrong; clear the cache and re-download"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}