Summary
mm uninstall --force against a live SQLite writer is well-defined on POSIX but has no contract on Windows. The current behavior is implicit and tracked nowhere.
POSIX (current)
shutil.rmtree on the state directory succeeds even while the writer holds an open BEGIN IMMEDIATE lock — POSIX unlink removes the directory entry while the inode lives until the last fd closes. The state directory ends up wiped; the lock-holding process keeps a dangling fd to a now-unlinked inode and eventually exits without further drama.
This is exercised by test_force_overrides_db_lock in packages/memtomem/tests/test_uninstall_cmd.py.
Windows (current — undefined)
Windows refuses to unlink a file held by an open handle:
[WinError 32] The process cannot access the file because it is being
used by another process: ...\.memtomem\memtomem.db
mm uninstall --force returns a non-zero exit and prints Deletion failed at .... The state dir is partially wiped (other files may be gone before the DB unlink fails) — the user is left in a half-cleaned state with no clear next step.
The POSIX-only contract test was skipped on Windows in #729 (cluster H-2 of #643) so CI can go green, but the underlying behavior question remains.
Open questions
-
What should --force mean on Windows when a writer is alive? Options:
- (a) Refuse with a Windows-specific error message: "close the writer first; --force cannot wipe an open file on Windows" — symmetric with the POSIX contract that
--force overrides the writer-detection refusal, but explicit about the platform limitation.
- (b) Best-effort partial wipe (current behavior) but with a clear warning + exit code.
- (c) Probe for the writer pid via the existing flock/lockfile path (which already powers refusal) and signal it before attempting the wipe — closer to POSIX semantics but requires kill/permission flow.
-
Should the state-dir wipe be transactional? The half-wipe failure mode on Windows leaks orphaned files (memories/, config.json) when the DB unlink fails. Worth considering staging the deletion (shutil.rmtree to a temp sibling, then atomic rename + cleanup) so partial failure leaves the user with the original tree intact.
Why this matters
Without an explicit contract, Windows users hitting --force against a live writer get a confusing partial failure. Worse, the test gap (covered only on POSIX) means future refactors of the uninstall path can silently regress Windows without anyone noticing.
Scope
Cross-refs
Summary
mm uninstall --forceagainst a live SQLite writer is well-defined on POSIX but has no contract on Windows. The current behavior is implicit and tracked nowhere.POSIX (current)
shutil.rmtreeon the state directory succeeds even while the writer holds an openBEGIN IMMEDIATElock — POSIXunlinkremoves the directory entry while the inode lives until the last fd closes. The state directory ends up wiped; the lock-holding process keeps a dangling fd to a now-unlinked inode and eventually exits without further drama.This is exercised by
test_force_overrides_db_lockinpackages/memtomem/tests/test_uninstall_cmd.py.Windows (current — undefined)
Windows refuses to
unlinka file held by an open handle:mm uninstall --forcereturns a non-zero exit and printsDeletion failed at .... The state dir is partially wiped (other files may be gone before the DB unlink fails) — the user is left in a half-cleaned state with no clear next step.The POSIX-only contract test was skipped on Windows in #729 (cluster H-2 of #643) so CI can go green, but the underlying behavior question remains.
Open questions
What should
--forcemean on Windows when a writer is alive? Options:--forceoverrides the writer-detection refusal, but explicit about the platform limitation.Should the state-dir wipe be transactional? The half-wipe failure mode on Windows leaks orphaned files (memories/, config.json) when the DB unlink fails. Worth considering staging the deletion (
shutil.rmtreeto a temp sibling, then atomic rename + cleanup) so partial failure leaves the user with the original tree intact.Why this matters
Without an explicit contract, Windows users hitting
--forceagainst a live writer get a confusing partial failure. Worse, the test gap (covered only on POSIX) means future refactors of the uninstall path can silently regress Windows without anyone noticing.Scope
/vs\\) #643 — the Windows CI sweep currently parks this as a documented skip.Cross-refs
/vs\\) #643 — Windows compat sweep umbrella