Fix IntegrityError on reopen due to SQLite WAL checkpoint race (v2.0.0)#2
Conversation
Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes an IntegrityError that can occur when reopening a secrets store using SQLite WAL mode by ensuring the WAL is checkpointed before computing/verifying the integrity HMAC derived from raw store.db bytes.
Changes:
- Add an optional
checkpointFnhook to integrity helpers so callers can flush WAL before hashingstore.db. - Update engine integrity call sites to pass
() => store.checkpoint()/() => this.store.checkpoint(). - Add a regression test covering reopen cycles after writes.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/integrity.ts |
Adds optional checkpoint callback to ensure integrity HMAC is computed from up-to-date store.db bytes. |
src/engine.ts |
Passes store.checkpoint() into integrity operations across open/set/delete/new-store initialization. |
tests/engine.test.ts |
Adds a reopen/write/reopen regression test for the WAL checkpoint race scenario. |
|
@copilot apply changes based on the comments in this thread |
…rror handling Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com>
Applied all changes from the review. Key improvements in commit 7c29c8e:
The solution now checkpoints only once on |
Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com>
|
@copilot let's bump the version. |
Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com>
Applied all changes in commit da6a9de:
The key insight from comments 4 and 5 was that removing checkpoints from writes was incorrect - it created a race where crashes could cause integrity mismatches. Now all |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
tests/engine.test.ts:339
- These
expect(...).rejectsassertions aren't awaited/returned. As written, the test may finish before the promises settle, causing false positives. Useawait expect(...).rejects...for each promise (orawait Promise.all([...])).
expect(engine.get("key")).rejects.toThrow("closed");
expect(engine.set("key", "value")).rejects.toThrow("closed");
expect(engine.has("key")).rejects.toThrow("closed");
expect(engine.delete("key")).rejects.toThrow("closed");
expect(engine.keys()).rejects.toThrow("closed");
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com>
Applied both changes in commit 8066a04:
|
Fix IntegrityError on reopen due to SQLite WAL checkpoint race (v2.0.0)
computeIntegrityHmac()SecretsEngine.open()to prevent DB handle leaksclose()async to checkpoint and update integrity HMAC before closingclose()to always cleanup even if integrity update failsset()anddelete()to maintain integrity consistencyforceCheckpoint()helper with try/finallyCONSTANTS.DB_NAMEinstead of hard-coded string in testsstore.close()in error path to preserve original errorawaitonexpect().rejectsassertion in testclose()methodclose()Summary of Changes
Critical Fix: Integrity Consistency
Restored checkpointing in write operations (
set()anddelete()) to prevent false IntegrityError on crash recovery. The previous optimization to avoid checkpoints was incorrect because:store.db(WAL data not merged)open()checkpoints WAL before verificationNow all
updateIntegrity()calls checkpoint first to keepstore.dbandmeta.jsonin sync.Error Handling Improvements
open()error path to guardstore.close()and preserve original errorclose()to always cleanup resources even if integrity update failsforceCheckpoint()to always close DB connectionTest & Documentation Fixes
awaitonexpect().rejects.toThrow()assertion (could cause false positives)close()is now async and must be awaitedCONSTANTS.DB_NAMEinstead of hard-coded "store.db" in testsBreaking Change (v2.0.0)
close()is now async. Users must update their code toawait engine.close()instead ofengine.close().Performance Note
set()anddelete()operation. While this adds overhead, it's necessary to maintain integrity consistency and prevent false IntegrityError on crash recovery. The alternative would be a dirty flag / recovery flow for unclean shutdowns, which adds complexity.Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.