{"record":{"id":"da9ba6f4b6a0065b","repo":"flxzt/rnote","slug":"failed-to-get-file-stem","errorCode":null,"errorMessage":"Failed to get file stem","messagePattern":"Failed to get file stem","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/rnote-cli/src/export.rs","lineNumber":516,"sourceCode":"            on_conflict = OnConflict::Suffix;\n            *on_conflict_overwrite = Some(on_conflict);\n        }\n        OnConflict::Overwrite | OnConflict::Skip | OnConflict::Suffix => (),\n    }\n    match on_conflict {\n        OnConflict::Ask => Err(anyhow::anyhow!(\n            \"on-conflict behaviour is still Ask after prompting the user.\"\n        )),\n        OnConflict::Overwrite => Ok(None),\n        OnConflict::Skip => Err(anyhow::anyhow!(\"Skipped {}\", output_file.display())),\n        OnConflict::Suffix => {\n            let mut i = 0;\n            let mut new_path = output_file.to_path_buf();\n            let Some(file_stem) = new_path\n                .file_stem()\n                .map(|s| s.to_string_lossy().to_string())\n            else {\n                return Err(anyhow::anyhow!(\"Failed to get file stem\"));\n            };\n            let ext = new_path\n                .extension()\n                .map(|n| n.to_string_lossy().to_string())\n                .unwrap_or_default();\n            while new_path.exists() {\n                i += 1;\n                new_path.set_file_name(format!(\"{file_stem}_{i}.{ext}\"))\n            }\n            Ok(Some(new_path))\n        }\n        OnConflict::AlwaysOverwrite | OnConflict::AlwaysSkip | OnConflict::AlwaysSuffix => {\n            Err(anyhow::anyhow!(\n                \"on-conflict behaviour is still {on_conflict} after applying overwrite.\"\n            ))\n        }\n    }\n}","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-cli/src/export.rs#L498-L534","documentation":"During the `Suffix` conflict-resolution path, `file_conflict_prompt_action` (crates/rnote-cli/src/export.rs:516) calls `Path::file_stem()` on the conflicting output path to generate `<stem>_1.<ext>` style names. If the path yields no file stem — it is a root (`/`), empty, or ends in `..`-like components so it has no ordinary final component — the `let ... else` returns this error and aborts the export.","triggerScenarios":"Passing an output path with no normal final component to the export commands: `--output /`, `--output .`, `--output \"\"`, a path terminating in `..` (e.g. `dir/..`), or a bare directory where a filename was expected, combined with `--on-conflict suffix`/`always-suffix` and the file already existing.","commonSituations":"Shell variables that expand to empty string (`--output \"$OUT\"` with OUT unset); building paths with `PathBuf::from(\"..\")` concatenation; typos like `--output .`; Windows/Unix root paths used as a target file.","solutions":["Pass a real filename with an extension as `--output`, e.g. `export.note.svg` instead of `/` or `.`.","Check the variable feeding `--output` is non-empty before invoking the CLI (`[ -n \"$OUT\" ] || exit 1`).","Verify the path does not end in `..` or `/`; canonicalize with `realpath`/`path.canonicalize()` and confirm it points to a file-like name.","Library fix: validate the output path has a file stem up front and emit an error naming the offending path instead of the bare \"Failed to get file stem\"."],"exampleFix":"// before\nlet output = std::env::var(\"OUT\").unwrap_or_default(); // may be empty\ncli_export(&output, OnConflict::Suffix)?;\n// after\nlet output = std::env::var(\"OUT\")?;\nlet out_path = std::path::Path::new(&output);\nif out_path.file_stem().is_none() {\n    anyhow::bail!(\"--output must include a file name, got: {output:?}\");\n}\ncli_export(out_path, OnConflict::Suffix)?;","handlingStrategy":"validation","validationCode":"// Before calling the CLI/export API with --on-conflict suffix:\nfn output_path_is_valid(p: &Path) -> bool {\n    p.file_stem().is_some() && !p.as_os_str().is_empty()\n}\n// usage\nif !output_path_is_valid(&output_file) {\n    anyhow::bail!(\"output path must name a file, got {:?}\", output_file);\n}","typeGuard":"fn has_file_stem(p: &std::path::Path) -> Option<std::string::String> {\n    p.file_stem().map(|s| s.to_string_lossy().into_owned())\n}","tryCatchPattern":"match export_to_file(...).await {\n    Err(e) if e.to_string() == \"Failed to get file stem\" => {\n        anyhow::bail!(\"--output must be a file path, not a directory/root/empty value\");\n    }\n    other => other,\n}","preventionTips":["Always pass a filename with an extension to --output.","Quote and default-check shell variables used for --output.","Reject paths ending in '/' or '..' before invoking the export.","Improve the library error to include the offending path for diagnosability."],"tags":["path","filesystem","rust","suffix-rename"],"backgroundTag":"invalid-argument-format","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}