Skip to content

file.mkdir ignores --conflict: anything that is not a directory at its path fails the deploy under every policy, while file.link and file.copy replace it #822

Closed

Description

@David-Noble-at-work

Feature: #836 — file provider. (Moved from #463: every line of the fix is in pkg/op/provider/file.)

The contract

--conflict=<policy> answers one question: what does writ do when something already exists at a target it is
about to write?

Policy If the target exists
stop refuse the deploy and list the targets
skip leave it, write nothing there
replace archive it, then write

What the occupant is — a file, a directory, a live link, a dangling link — does not enter into it. "It exists" is the
whole test. That is what any developer, operator, or administrator reads into the flag, and it is how two of writ's
three writers behave.

The defect

file.mkdir: C:\Users\david-noble\.config\git exists, but is not a directory
  • file.link reads the policy (provider.go:234) and under replace archives the occupant and writes the link.
  • file.copy does the same (provider.go:2112).
  • file.mkdir never reads the policy (provider.go:363, :380). Anything at its path that is not a directory is
    an error, under stop, skip, and replace alike.

On the machine the occupant was a stale symlink from the previous dotfile manager. It could have been anything.
The run, invoked with --conflict=replace, stopped on a target the user had told it to replace.

The fix

file.mkdir applies the conflict policy exactly as file.link does:

Acceptance

  • file.mkdir over a regular file, a live symlink, and a dangling symlink: each policy produces the outcome in
    the table, with a unit test per cell
  • the receipt of a replacing mkdir carries the archive id; compensation restores the occupant
  • writ deploy noblefactor --conflict=replace on DANOBLE-WD11-3 gets past file.mkdir-6

Related

#831 — the stop refusal list omits directory targets for the same reason. #813, #814, #818, #819, #820, #821 —
the same machine, the same day.

Ticked 2026-09-21, all three. The third at 06:02 on DANOBLE-WD11-3, build ad2219ef with #900: writ deploy common noblefactor-ops --conflict=replace deployed 56 links, exit 0; C:\Users\david-noble\.config\git, the 2022 symlink, is archived in recovery (01a0c1e9-f911-…) and the path is a directory holding five links. Log: build/e2e/2026-09-21-windows-danoble-wd11-3-lane14-deploy-2.log in the worktree. In
pkg/op/provider/file/provider.go: Mkdir observes the leaf and lets the policy govern a non-directory occupant;
archiveMkdirOccupant discovers, archives, and marks the occupant gone before the claim; the receipt is create_dir
with the recovery id and digest; compensateMakeDir restores through restoreDisplacedOccupant. Tests in
provider_test.go: TestMkdir_Stop_RefusesAnOccupant, TestMkdir_Skip_LeavesAnOccupant,
TestMkdir_Replace_ArchivesAnOccupantAndCreates (a file, a live symlink, a dangling symlink each),
TestMkdir_Replace_CompensationRestoresTheOccupant, TestMkdir_ADirectoryIsNeverAConflict. On
danoble-ud24-1.local, writ deploy common noblefactor-ops --conflict=replace deployed 127 links where 2026-09-18's
run stopped at file.mkdir. Plan: docs/plans/fix/822-mkdir-obeys-conflict.md.

Activity

  1. added
    bugSomething isn't working
    Area:CLICommand-line interface
    Epic:WritDeploymentwrit deployment: layers, adoption, and drift
    Severity:HighA supported path is broken with no workaround
    on Sep 5, 2026
  2. changed the title [-]A dangling symlink where a directory must be created aborts the deploy: file.mkdir refuses it and pre-flight never counted it as an occupant[/-] [+]file.mkdir ignores --conflict: anything that is not a directory at its path fails the deploy under every policy, while file.link and file.copy replace it[/+] on Sep 5, 2026
  3. added
    Epic:ProvidersThe provider surface: one feature per provider
    and removed
    Epic:WritDeploymentwrit deployment: layers, adoption, and drift
    on Sep 5, 2026
  4. David-Noble-at-work commented on Sep 8, 2026

    @David-Noble-at-work
    ContributorAuthor

    Another instance, 2026-09-08, DANOBLE-WD11-3, build 919b952, the first deploy since the layer move:

    [writ] [△] scope home failed: … child "file.mkdir-6": file.mkdir: C:\Users\david-noble\.config\git exists, but is not a directory
    Error: 1 scope(s) failed
    

    ~/.config/git was a dangling stow-era symlink (its target under Home\all\… has not existed since August). Under --conflict=replace, file.link and file.copy would have archived and replaced it; file.mkdir failed the whole scope instead. The pre-flight (#831) listed nothing, since the target is a directory's. Removing the dead link by hand let the deploy proceed.

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

Metadata

Metadata

Labels

Area:CLICommand-line interfaceEpic:ProvidersThe provider surface: one feature per providerPriority:P1This cycleSeverity:HighA supported path is broken with no workaroundbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions