Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable changes to ShellClaw are documented here. Format follows [Keep a Cha
## [Unreleased]

### Fixed
- `memory_init` no longer deletes an existing SQLite DB when `sqlite3_open` fails (permissions or transient I/O).
- Anthropic `content` parse fails closed when growing the text buffer or `tool_use` array cannot `realloc`, instead of copying against an inflated cap.
- HTTP 200 JSON-RPC results with a malformed ASAP envelope no longer double-free the duplicated request id.
- Inbound ASAP `mcp.tool_call` and `state.query` now hold `agent_lock()` around tool execute and SQLite `g_db` reads, matching `task.request`.
Expand Down
7 changes: 7 additions & 0 deletions src/core/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ int memory_init(const char *path)
int file_existed = path_exists(path);
int recreated = 0;
if (sqlite3_open(path, &g_db) != SQLITE_OK) {
/* Never delete an existing DB on open failure (permissions, transient I/O). */
if (file_existed) {
fprintf(stderr, "Error: cannot open existing memory DB at %s: %s\n",
path, g_db ? sqlite3_errmsg(g_db) : "unknown");
if (g_db) { sqlite3_close(g_db); g_db = NULL; }
return -1;
}
if (g_db) { sqlite3_close(g_db); g_db = NULL; }
remove(path);
if (sqlite3_open(path, &g_db) != SQLITE_OK) {
Expand Down
38 changes: 38 additions & 0 deletions tests/test_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "sqlite3.h"

Expand Down Expand Up @@ -153,11 +154,48 @@ static int test_gateway_schema_migration_v01(void)
return 0;
}

static int test_existing_db_preserved_on_open_failure(void)
{
const char *path = "/tmp/shellclaw_test_open_fail.db";
sqlite3 *probe = NULL;
int open_rc;
remove(path);
ASSERT(memory_init(path) == 0);
ASSERT(memory_save("preserve", "important data", NULL) == 0);
memory_cleanup();
ASSERT(chmod(path, 0000) == 0);
open_rc = sqlite3_open(path, &probe);
if (probe)
sqlite3_close(probe);
if (open_rc == SQLITE_OK) {
/* Root / DAC override: mode 000 still opens. Do not delete. */
(void)chmod(path, 0600);
remove(path);
return 0;
}
if (memory_init(path) != -1) {
fprintf(stderr, "FAIL: %s:%d memory_init(path) == -1\n", __FILE__, __LINE__);
(void)chmod(path, 0600);
memory_cleanup();
remove(path);
return 1;
}
ASSERT(chmod(path, 0600) == 0);
ASSERT(memory_init(path) == 0);
char buf[256];
ASSERT(memory_recall("important", buf, sizeof(buf), 5) == 0);
ASSERT(strstr(buf, "important data") != NULL);
memory_cleanup();
remove(path);
return 0;
}

int main(void)
{
RUN(test_schema_and_fts5());
RUN(test_save_overwrite());
RUN(test_corrupted_db_recreated());
RUN(test_existing_db_preserved_on_open_failure());
RUN(test_session_list());
RUN(test_session_crud());
RUN(test_gateway_schema_new_db());
Expand Down
Loading