Skip to content

mkdir doesn't error on already-existing directory #19003

Closed

Description

@pickx

Bug report form

  • I have done a basic search of the issue tracker to find any existing issues that are similar.
  • I have checked that my version is at least the latest stable release available via my installation method.

Describe the bug

mkdir for an existing path, including a path created by mkdir itself, returns exit code 0 as if a directory was created.
worse, mkdir --verbose reports the path as created: true.

to be clear, this doesn't match the behavior of GNU/coreutils mkdir or uutils/coreutils mkdir

How to reproduce

> cd (mktemp --directory)
> mkdir foo
> $ans.exit_code
0
> mkdir foo
> $ans.exit_code
0
> mkdir --verbose foo
╭───┬─────────────────────────────────────────────────────────────────────┬─────────┬───────╮
│ # │                                path                                 │ created │ error │
├───┼─────────────────────────────────────────────────────────────────────┼─────────┼───────┤
│ 0 │ /var/folders/n1/2s4wgvw17z75hspycqvqh8k00000gn/T/tmp.F2LC1Klmii/foo │ true    │       │
╰───┴─────────────────────────────────────────────────────────────────────┴─────────┴───────╯
> ^/opt/homebrew/opt/uutils-coreutils/libexec/uubin/mkdir foo
mkdir: foo: File exists

Expected behavior

error on the existing path. exit code should be 1 if at least one path was not created because it already exists (even though other paths may have been created). this matches uutils

also, mkdir --verbose should show it as an error.

Configuration

key value
version 0.115.2
major 0
minor 115
patch 2
branch
commit_hash f8ae9ac
build_os macos-aarch64
build_target aarch64-apple-darwin
rust_version rustc 1.96.1 (31fca3adb 2026-06-26)
rust_channel 1.96.1-aarch64-apple-darwin
cargo_version cargo 1.96.1 (356927216 2026-06-26)
build_time 2026-09-06 00:17:18 +03:00
build_rust_channel release
allocator standard
features default, lsp, mcp, network, plugin, rustls-tls, sqlite, trash-support
installed_plugins desktop_notifications 0.107.0, formats 0.107.0, highlight 1.4.9+0.107.0, json_path 0.17.1, query 0.107.0, regex 0.19.0, skim 0.22.0, strutils 0.21.0
experimental_options example=false, dc-glob=true, reorder-cell-paths=true, pipefail=true, enforce-runtime-annotations=true, native-clip=true, cell-path-types=true, background-completions=true

Activity

  1. added this to the v0.116.0 milestone on Sep 10, 2026
  2. added a commit that references this issue on Sep 10, 2026
    954e34d
  3. Developer1010x commented on Sep 13, 2026

    @Developer1010x
    Contributor

    I had a look at this. The cause is one line, but the fix is a design decision rather than a bug fix, so flagging it before writing code.

    crates/nu-command/src/filesystem/umkdir.rs hard-codes

    const IS_RECURSIVE: bool = true;

    and passes it as Config { recursive: IS_RECURSIVE, .. }. In uu_mkdir the "File exists" error is raised only when recursion is off:

    // uu_mkdir 0.11.0, src/mkdir.rs:213
    if path_exists && !config.recursive {
        return Err(USimpleError::new(1, translate!("mkdir-error-file-exists", ...)));
    }

    With recursive: true the existing-directory case instead falls through to an Ok(()) branch, which is where the silent success comes from.

    So mkdir in nushell is always mkdir -p, which is what the description ("with intermediary directories if required") and the creates_intermediary_directories test both say. The comparison in the report is against non--p mkdir — uutils mkdir -p foo on an existing foo also exits 0 and prints nothing, as POSIX requires -p to suppress that error. So "match uutils" points in two different directions depending on which mkdir we mean.

    That leaves roughly three options:

    1. Add --parents/-p and make the default non-recursive. Exactly matches coreutils. Also breaks every mkdir a/b/c in every existing script and config, and creates_intermediary_directories with it. A large breaking change for a command people use constantly.
    2. Keep creating intermediates, but error if the final target already exists. Gives the behavior asked for here. It is not coreutils semantics under any flag combination, and it needs an explicit existence check on the nushell side, since uu_mkdir will not report it in recursive mode.
    3. Leave the behavior and document that nushell's mkdir is mkdir -p.

    One part is a plain bug under all three, though: mkdir --verbose reports created: true for a directory that already existed. Whatever is decided about the exit code, that field is simply wrong — nothing was created. It can be fixed on its own by checking existence before the call and reporting created: false.

    Happy to implement whichever direction you prefer. If it helps to make progress in the meantime, I can put up a small PR for the --verbose created field alone, since that one does not depend on the decision above.

  4. Developer1010x commented on Sep 13, 2026

    @Developer1010x
    Contributor

    Put up #19017 for the --verbose created field on its own, since that part does not depend on the decision above. The exit-code question is still open — happy to follow up with whichever of the three options you land on.

  5. sharmis-tp commented on Nov 12, 2026

    @sharmis-tp

    I'd like to take this. Will add an existence check in mkdir before the call.

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

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions