Skip to content

error: Uncaught (in promise) AlreadyExists: File exists (os error 17): symlink #76

Open

Description

@magnusviri
sudo /usr/local/bin/pkgm install pipenv
error: Uncaught (in promise) AlreadyExists: File exists (os error 17): symlink 'python' -> '/usr/local/pkgs/pipenv.pypa.io/v3000.0.0/venv/bin/python3'
  Deno.symlinkSync(src, dst);
       ^
    at Object.symlinkSync (ext:deno_fs/30_fs.js:519:3)
    at symlink_with_overwrite (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:501:8)
    at processEntry (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:375:7)
    at async processEntry (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:363:9)
    at async processEntry (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:363:9)
    at async processEntry (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:363:9)
    at async mirror_directory (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:350:3)
    at async install (file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:153:5)
    at async file:///Users/james/.pkgx/pkgx.sh/pkgm/v0.11.1/bin/pkgm:65:20

I get this a lot. I have to fix it by just rm -r'ing whatever it says already exists.

Activity

  1. transferred this issue frompkgxdev/pkgxon Jan 20, 2026
  2. jhheider commented on Jan 20, 2026

    @jhheider
    Contributor

    here's the relevant lines:

    pkgm/pkgm.ts

    Lines 497 to 502 in e3934de

    function symlink_with_overwrite(src: string, dst: string) {
    if (existsSync(dst) && Deno.lstatSync(dst).isSymlink) {
    Deno.removeSync(dst);
    }
    Deno.symlinkSync(src, dst);
    }

    notably, the trap checks that the thing it's removing is a symlink before removing it. soooooo... that suggests there's something other than a symlink there. next time you see this, can you ls -l $whatever and check? that trap should probably exit gracefully with a better error message if it finds something other than a symlink.

    notably, this will fail with a hard link (i.e. "a file"). so we might be hitting that if it's hardlinking to deal with a boundary issue. as it can do on line 371:

    pkgm/pkgm.ts

    Lines 352 to 380 in e3934de

    async function processEntry(sourcePath: string, targetPath: string) {
    const fileInfo = await Deno.lstat(sourcePath);
    if (fileInfo.isDirectory) {
    // Create the target directory
    await ensureDir(targetPath);
    // Recursively process the contents of the directory
    for await (const entry of Deno.readDir(sourcePath)) {
    const entrySourcePath = join(sourcePath, entry.name);
    const entryTargetPath = join(targetPath, entry.name);
    await processEntry(entrySourcePath, entryTargetPath);
    }
    } else if (fileInfo.isFile) {
    // Remove the target file if it exists
    if (existsSync(targetPath)) {
    await Deno.remove(targetPath);
    }
    // Create a hard link for files
    await Deno.link(sourcePath, targetPath);
    } else if (fileInfo.isSymlink) {
    // Recreate symlink in the target directory
    const linkTarget = await Deno.readLink(sourcePath);
    symlink_with_overwrite(linkTarget, targetPath);
    } else {
    throw new Error(`unsupported file type at: ${sourcePath}`);
    }
    }
    }

  3. magnusviri commented on Feb 14, 2026

    @magnusviri
    Author

    I figured this out. existsSync(dst) is returning false when Deno.lstatSync(dst).isSymlink returns true, and it looks like it's because dst is a link to a file that doesn't exist. So existsSync(dst) must be following the link and checking that file if it exists.

  4. jhheider commented on Feb 14, 2026

    @jhheider
    Contributor

    Fascinating and brutal. So, perhaps either the existsSync() is purely redundant, or perhaps we should just switch the ||. || means it'll remove hard links (which is good), but also means it will remove files we didn't install (which is bad).

  5. magnusviri commented on Feb 14, 2026

    @magnusviri
    Author

    I didn't see your comment until just now. The pull request is what ChatGPT said to do.

  6. magnusviri commented on Feb 14, 2026

    @magnusviri
    Author

    Sigh, I'm still getting this error. I'll debug it some more next time I work on this.

  7. jhheider commented on Feb 14, 2026

    @jhheider
    Contributor

    The answer is probably:

    If deno lstat issymlink then remove
    After that, if exists then we need to do something. Probably error unless we have a --force flag, is my guess. Since it'll remove any file. Not the biggest problem in /usr/local, but potentially lossy. Could delete something someone wanted.

  8. added a commit that references this issue on Feb 17, 2026
    6adc003
  9. jhheider commented on Feb 17, 2026

    @jhheider
    Contributor

    @magnusviri see if #79 fixes it for you. this tries to take a more considered approach:

    • if it's a symlink, confidently remove it
    • if it still exists afterward, remove it iff -f/--force was passed
    • otherwise, complain with a (hopefully) helpful error message
  10. magnusviri commented on Feb 20, 2026

    @magnusviri
    Author

    It fixed one instance of the problem. I'll keep testing it and let you know if it causes me problems.

  11. jhheider commented on Feb 20, 2026

    @jhheider
    Contributor

    result! i'm hopeful. i didn't want it to smash stuff you wanted, but you should have some recourse to move forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions