Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

`espup update` fails with `File exists (os error 17)` when `~/.espup/esp-clang` is a dangling symlink

Closed Beginner friendly
#580 1 comment 0 reactions 0 assignees View on GitHub

@SergioGasquez is already working on this.

Since Oct 13, 2026.

  • #581 by @SergioGasquez — open

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

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 in impl Installable for Llvm::install)
  • Note: the same buggy guard is also present on main (verified against v0.18.0 and current main).

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

  1. Run an espup install/update that creates the Unix symlink
    ~/.espup/esp-clang -> <rustup>/.../xtensa-esp32-elf-clang/<OLD_LLVM>/esp-clang/lib.
  2. 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. the esp-clang/lib
    subpath no longer exists.
    • The ~/.espup/esp-clang symlink is not updated/removed and now points
      at a non-existent target (dangling).
  3. Run espup update again.

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

  1. 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.)

  2. Replace remove_dir_all with remove_file for 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

Open in Codespaces

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from esp-rs/espup

All issues in esp-rs/espup

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.