fix(memory): preserve existing DB when sqlite3_open fails - #63
Conversation
Skip remove() when sqlite3_open fails on a path that already exists. Deleting it caused silent data loss on permissions or transient I/O. Refs: #63
ac3c72e to
c6717f3
Compare
There was a problem hiding this comment.
Stale comment
Could not approve: GitHub rejects a self-approval because this bot authored the PR. Cursor Bugbot and Cursor Security Agent both passed with no findings that need human review. adriannoes is already assigned; no additional reviewers were added.
Sent by Cursor Approval Agent: Pull Request Router and Approver
There was a problem hiding this comment.
Summary
ShellClaw is a C99 edge agent (Jetson / Raspberry Pi) with sessions, long-term memories, and cron in one SQLite file. This PR closes the remaining data-loss hole in memory_init: commit 4268d69 already refused to delete an existing DB on schema failure (CR-18), but sqlite3_open failure still called remove() and retried. Five lines in src/core/memory.c now return -1 when path_exists was true, matching that schema-mismatch path. init_subsystems already aborts startup on that -1. I ran test_memory as uid 1000; all tests passed, including the new case.
Must Fix
None. Production behavior matches the PR claim. Missing-path open failures still unlink and retry. No injection, secrets, or new coupling.
Should Fix
src/core/memory.c(newfprintfon the open-fail path): capturesqlite3_errmsg(g_db)beforesqlite3_close. After close the message is gone. Operators on EACCES or I/O currently get only the path, then bootstrap's genericmemory init failed.tests/test_memory.ctest_existing_db_preserved_on_open_failure:chmod(path, 0000)does not blocksqlite3_openas root.sudo ./build/test_memoryfails atASSERT(memory_init(path) == -1). GitHububuntu-24.04is non-root, so required CI should pass. Skip whengeteuid()==0, or occupy the path with a directory (sqlite3_openfails even as root; the oldremove()of an empty dir would still recreate). Restore0600if an ASSERT fires after chmod; a failed root run left a mode-000 file in/tmp.
Nice to Have
- Document fail-closed existing files in
memory.h(memory_initstill only says it creates the file if missing). - A
SHELLCLAW_TESTopen-fail hook would be root-proof, same idea as the Anthropic realloc hook in #80. test_corrupted_db_recreatedstill has a name that implies recreate. Pre-existing; garbage files usually fail inrun_schema, notsqlite3_open.
Positive Highlights
- Same
file_existedgate as the schema path. No new control-flow style. - The regression test would have been red on the old unlink-and-recreate: the owner can unlink a mode-000 file they own, then
sqlite3_opencreates an empty DB and init returns 0 with data gone. - Isolated to
memory.c+test_memory.c, CHANGELOG Unreleased Fixed line in a separate docs commit, Conventional Commits, no secrets.
Sent by Cursor Automation: Adrianno鈥檚 personal code review
Capture sqlite3_errmsg before close. Skip the chmod-000 case when sqlite3_open still succeeds (root/DAC), and restore mode 0600 if the init-fail assertion fires. Refs: #63
There was a problem hiding this comment.
Could not approve: GitHub rejects a self-approval because this bot authored the PR. Cursor Bugbot and Cursor Security Agent both passed with no findings that need human review. adriannoes is already assigned; no additional reviewers were added.
Sent by Cursor Approval Agent: Pull Request Router and Approver


Summary
memory_initno longerremove()s an existing SQLite file whensqlite3_openfails. Permissions or transient I/O no longer destroy sessions, long-term memories, or cron jobs.development(includes fix(providers): fail closed when Anthropic parse realloc fails聽#80). Not a cherry-pick of the original Bot commit.src/core/memory.candtests/test_memory.c. Missing-file open failures still unlink and retry.Test plan
test_existing_db_preserved_on_open_failure(red: chmod 000 open-fail path deleted and recreated the DB; green: init returns -1 and data is intact)make test_memoryCI=true make testmake staticRefs: #63
Note
Medium Risk
Changes startup behavior for the persistent memory store; failures now fail closed without wiping data, which is safer but leaves the agent unable to start until the DB is readable again.
Overview
memory_initno longer deletes an existing SQLite file whensqlite3_openfails (e.g. permissions or transient I/O). It logs the error, closes the handle, and returns-1instead ofremove()+ recreate. The unlink-and-retry path is unchanged when the DB path did not exist before open.A new unit test (
test_existing_db_preserved_on_open_failure) chmods the DB to block opens, asserts init fails without data loss, then restores permissions and verifies recall still works. CHANGELOG documents the fix.Reviewed by Cursor Bugbot for commit 5f8d420. Bugbot is set up for automated code reviews on this repo. Configure here.