Skip to content

Windows: _stash_deferred_artifacts shutil.move raises FileExistsError when a story re-defers #101

Description

@pbean

Summary

_stash_deferred_artifacts (engine.py, ~line 2338) moves a deferred story's spec into run_dir/deferred/<story_key>/<spec_name> via shutil.move. On the same filesystem that falls back to os.rename, which on Windows raises FileExistsError when the target already exists — i.e. when the same story defers a second time with the same spec filename. POSIX rename(2) silently overwrites, which is why this has never bitten on Linux/macOS.

This is another instance of the "POSIX-safe idiom, Windows-unsafe" class from #74 (H-list), surfaced while auditing PR #98 — it's the one rename-over site in the package that #98's atomic_replace sweep did not cover (the target is a possibly-existing file, not a fresh temp-rename).

Fix direction

Give the move overwrite semantics and the win32 retry in one shot: copy/write into a temp path inside the dest dir, then route through platform_util.atomic_replace (which wraps os.replace — overwriting — with the win32 sharing-violation retry from #98). Alternatively os.replace directly if the source is same-filesystem-guaranteed.

Notes

  • The stash is a write-only forensic artifact (no reader recomposes the path), so the blast radius is the defer flow crashing mid-run on Windows, not data corruption.
  • The two stdlib-only hook scripts' os.replace calls (data/bmad_loop_hook.py, data/bmad_loop_probe_hook.py) were also checked and are fine as-is: time_ns-unique target names, never concurrently read, and they can't import the package by design.

Refs: #74, #98

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions