Summary
A reconciliation failed transiently because PV observed pv.db-wal, then SQLite removed the ephemeral WAL before PV could chmod/validate it:
state error: filesystem error at ~/.pv/pv.db-wal:
No such file or directory (os error 2)
The next reconciliation started normally 12 seconds later. The evidence points to a TOCTOU race in database-file permission hardening rather than database corruption.
Current code path
Database::open_with_migrations opens SQLite and calls secure_database_files before and after connection configuration/migrations: database.rs.
secure_database_files checks each database/WAL/SHM path for existence and then performs separate chmod/metadata operations: fs.rs.
The database-side files include ephemeral pv.db-wal and pv.db-shm: fs.rs.
The Project config watcher opens a writable/migrating Database every 100 ms poll: watcher.rs, increasing opportunities for the race.
Likely sequence:
path_exists(pv.db-wal) returns true.
- SQLite checkpoints/closes and removes the WAL.
secure_sensitive_file(pv.db-wal) receives ENOENT.
- The whole database open/reconciliation fails.
Expected behavior
Disappearance of SQLite's optional WAL/SHM files during secure inspection should be handled as a benign race. Real permission, ownership, or I/O failures must remain errors, and the persistent main database must still be hardened.
Acceptance criteria
- A WAL or SHM file disappearing between discovery and chmod/metadata validation does not fail
Database::open.
- A missing persistent
pv.db after creation/open is not silently ignored.
- Permission and owner mismatches on existing database-side files still fail clearly.
- Add deterministic coverage that removes an auxiliary file between existence observation and the security operation.
- Preserve user-only permissions for database, WAL, and SHM whenever those files exist.
- Verify Project watcher polling cannot create failed reconciliation jobs from this race.
- Avoid broad retry loops or weakening filesystem ownership checks.
Related
Summary
A reconciliation failed transiently because PV observed
pv.db-wal, then SQLite removed the ephemeral WAL before PV could chmod/validate it:The next reconciliation started normally 12 seconds later. The evidence points to a TOCTOU race in database-file permission hardening rather than database corruption.
Current code path
Database::open_with_migrationsopens SQLite and callssecure_database_filesbefore and after connection configuration/migrations:database.rs.secure_database_fileschecks each database/WAL/SHM path for existence and then performs separate chmod/metadata operations:fs.rs.The database-side files include ephemeral
pv.db-walandpv.db-shm:fs.rs.The Project config watcher opens a writable/migrating
Databaseevery 100 ms poll:watcher.rs, increasing opportunities for the race.Likely sequence:
path_exists(pv.db-wal)returns true.secure_sensitive_file(pv.db-wal)receivesENOENT.Expected behavior
Disappearance of SQLite's optional WAL/SHM files during secure inspection should be handled as a benign race. Real permission, ownership, or I/O failures must remain errors, and the persistent main database must still be hardened.
Acceptance criteria
Database::open.pv.dbafter creation/open is not silently ignored.Related
pv updateincurs a 10-second launchd throttle after switching app binary #300