`espup update` fails with `File exists (os error 17)` when `~/.espup/esp-clang` is a dangling symlink
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- rust
- Domain
- embedded-iot, tooling
Research direction
Start in src/toolchain/llvm.rs, in Llvm::install, and inspect the guard that removes ~/.espup/esp-clang before creating the symlink. Check how filesystem metadata and removal are handled for dangling symlinks and directories. Update the guard so a stale link can be replaced without deleting its target; done when espup update completes with a dangling link present and existing LLVM files remain intact.
Written by the indexing model from the issue text.
Description
espup update fails with File exists (os error 17) when ~/.espup/esp-clang is a dangling symlink
Environment
- OS: Linux (x86_64)
- espup version:
0.18.0 - Affected code:
src/toolchain/llvm.rs(symlink creation inimpl Installable for Llvm::install) - Note: the same buggy guard is also present on
main(verified againstv0.18.0and currentmain).
Summary
espup update aborts with File exists (os error 17) while creating the
~/.espup/esp-clang symlink, when that symlink already exists but points to a
path that no longer exists (a dangling symlink).
Steps to reproduce
- Run an
espupinstall/update that creates the Unix symlink
~/.espup/esp-clang -> <rustup>/.../xtensa-esp32-elf-clang/<OLD_LLVM>/esp-clang/lib. - Later, the LLVM toolchain is upgraded to a new version (e.g.
esp-20.1.1_...
->esp-21.1.3_...). The old LLVM install directory ends up incomplete /
removed, leaving only a failed partial download such as
idf_tool_xtensa_elf_clang.libs.tar.xz.part— i.e. theesp-clang/lib
subpath no longer exists.- The
~/.espup/esp-clangsymlink is not updated/removed and now points
at a non-existent target (dangling).
- The
- Run
espup updateagain.
Actual behavior
[info]: Creating symlink between '.../esp-21.1.3_20260408/esp-clang/lib' and '/home/angel/.espup/esp-clang'
[warn]: Installation for 'LLVM' failed, retrying. Error: File exists (os error 17)
[warn]: Previous installation of LLVM exists in: '.../esp-21.1.3_20260408'. Reusing this installation
... (retry loop) ...
Error: × File exists (os error 17)
The update never completes successfully.
Expected behavior
espup should remove any pre-existing ~/.espup/esp-clang (symlink, file, or
directory) before creating the new one, and the update should finish.
Root cause
In src/toolchain/llvm.rs the removal guard is:
let llvm_symlink_path = espup_dir.join("esp-clang");
if llvm_symlink_path.exists() {
remove_dir_all(&llvm_symlink_path)
.await
.map_err(|_| Error::RemoveDirectory(llvm_symlink_path.display().to_string()))?;
}
symlink(self.get_lib_path(), llvm_symlink_path)?;
Bug 1 — the trigger (EEXIST):
Path::exists() resolves the symlink and checks the target. For a dangling
symlink the target is gone, so exists() returns false and the
remove_dir_all branch is skipped. The stale symlink stays in place, and the
subsequent symlink() call fails with EEXIST (os error 17).
Confirmed on the reporter's machine: the old LLVM directory
.../xtensa-esp32-elf-clang/esp-20.1.1_20250829/ contained only
idf_tool_xtensa_elf_clang.libs.tar.xz.part — there was no esp-clang/lib — so
~/.espup/esp-clang was a dangling symlink, and exists() returned false.
Bug 2 — latent data-loss hazard:
Even when the guard does fire (non-dangling symlink), it uses
remove_dir_all. On a symlink pointing to a directory, remove_dir_all follows
the link and deletes the target directory's contents (failing only at the
final rmdir of the link). For a symlink into a real LLVM install this could
wipe the LLVM lib files. Removing a symlink/file should use remove_file, not
remove_dir_all.
Proposed fix
-
Detect the symlink/node itself, not its resolved target, e.g.:
use std::os::unix::fs::symlink_metadata; if llvm_symlink_path.symlink_metadata().is_ok() { remove_file(&llvm_symlink_path) .await .map_err(|_| Error::RemoveFile(llvm_symlink_path.display().to_string()))?; } symlink(self.get_lib_path(), llvm_symlink_path)?;(
Path::is_symlink()/symlink_metadata().is_ok()both avoid resolving the
link, so dangling symlinks are correctly detected and removed.) -
Replace
remove_dir_allwithremove_filefor removing the symlink/node.
Workaround
Manually delete the stale symlink, then re-run:
rm -f ~/.espup/esp-clang
espup update
- Dominant language
- Rust
- Stars
- 460
- Forks
- 37
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 6
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- No pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from esp-rs/espup
-
Difficulty 4/5 3-5 days Newbie friendliness 30/100
-
enhancement feature request
Difficulty 4/5 3-5 days Newbie friendliness 35/100
-
Install To Platform-Standard Locations | Reduce Home Directory ClutterMay be free again A pull request for this issue was closed without being merged. Openenhancement feature request
Difficulty 4/5 3-5 days Newbie friendliness 35/100
-
enhancement feature request
Difficulty 3/5 1-2 days Newbie friendliness 42/100
-
enhancement feature request
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
wormhole-foundation/wormhole#5062 ·
Maintainers usually reply within 1 day
-
RUSTSEC-2026-0348: Brotli certificate decompression can allocate up to 1 GiB before authenticationOpen
Difficulty 2/5 1-3 hours Newbie friendliness 60/100
-
Difficulty 1/5 1-3 hours Newbie friendliness 62/100
-
core: Amount::from_str panics on values above 21M BTC instead of returning an errorPossibly taken @bhuvan-somisetty claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
crate: spate-s3
Difficulty 2/5 Half a day Newbie friendliness 62/100
Maintainers usually reply within 1 day