# How Configuration Keys Work in the _config.db SQLite Store: Complete Technical Guide

> Explore how configuration keys work in the _config.db SQLite store. Learn about atomic reads, writes, and type-safe helpers in this technical guide.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: technical-guide
- Published: 2026-07-10

---

**Configuration keys in `codebase-memory-mcp` are stored as simple text-to-text mappings in a SQLite database named `_config.db`, accessed through a C wrapper API that provides atomic reads, writes, and type-safe convenience helpers.**

The `codebase-memory-mcp` tool persists user-editable runtime settings in a lightweight SQLite database rather than flat files. Understanding how these configuration keys are structured, stored, and retrieved—particularly within the `_config.db` file—is essential for extending the CLI or debugging runtime behavior.

## Database Schema and Storage Location

By default, the configuration database resides at `~/.cache/codebase-memory-mcp/_config.db`. You can override this location by setting the **`CBM_CACHE_DIR`** environment variable before launching the tool.

The database contains a single table called **`config`** with a minimal schema designed for key-value storage:

| Column | Type | Description |
|--------|------|-------------|
| `key` | `TEXT PRIMARY KEY` | The configuration identifier (e.g., `auto_index`, `ui-lang`). |
| `value` | `TEXT` | The setting stored as a plain string. |

The table is created lazily the first time the configuration store is accessed. In [`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c) at line 2716, the initialization logic executes:

```c
/* src/cli/cli.c – line 2716 */
const char *sql = "CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT)";
sqlite3_exec(cfg->db, sql, NULL, NULL, &err);

```

## The Configuration API

All interactions with `_config.db` flow through a small wrapper layer implemented in **[`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c)** and declared in **[`src/cli/cli.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.h)**. This abstraction handles connection management, prepared statement caching, and type conversion.

### Core Functions

The public API exposes the following functions:

- **`cbm_config_open(const char *cache_dir)`** – Opens or creates `_config.db` in the specified directory and returns a `cbm_config_t *` handle.
- **`cbm_config_close(cbm_config_t *cfg)`** – Finalizes the SQLite connection and frees resources.
- **`cbm_config_get(cbm_config_t *cfg, const char *key, const char *default_val)`** – Reads a key, returning `default_val` if the key is absent.
- **`cbm_config_set(cbm_config_t *cfg, const char *key, const char *value)`** – Inserts or updates a key-value pair atomically.
- **`cbm_config_delete(cbm_config_t *cfg, const char *key)`** – Removes a key from the store.

### Type Conversion Helpers

Because SQLite stores everything as text, the API provides convenience functions that coerce string values into native C types:

- **`cbm_config_get_bool(cbm_config_t *cfg, const char *key, bool default_val)`** – Parses `"true"` or `"false"` strings into boolean values.
- **`cbm_config_get_int(cbm_config_t *cfg, const char *key, int default_val)`** – Converts numeric strings to integers.

These helpers are defined alongside the core API in [`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c) (lines 2696–2812).

## Canonical Key Definitions

To prevent typos and provide a single source of truth, configuration keys are defined as macros in **[`src/cli/cli.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.h)** at lines 306–309:

```c
/* src/cli/cli.h – lines 306-309 */
#define CBM_CONFIG_AUTO_INDEX       "auto_index"
#define CBM_CONFIG_AUTO_INDEX_LIMIT "auto_index_limit"
#define CBM_CONFIG_AUTO_WATCH       "auto_watch"
#define CBM_CONFIG_UI_LANG          "ui-lang"

```

Throughout the codebase, these constants are used instead of raw strings. For example, in [`src/mcp/mcp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) at lines 6178 and 6272, the MCP server queries feature flags:

```c
/* src/mcp/mcp.c – line 6178 */
return cbm_config_get_bool(srv->config, CBM_CONFIG_AUTO_WATCH, true);

/* src/mcp/mcp.c – line 6272 */
auto_index = cbm_config_get_bool(srv->config, CBM_CONFIG_AUTO_INDEX, false);

```

## SQL Implementation and Prepared Statements

The wrapper API caches prepared SQLite statements in the `cbm_config_t` structure to avoid recompilation overhead on repeated accesses.

### Reading Values

Read operations use a parameterized `SELECT` statement defined at line 2749 in [`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c):

```c
/* src/cli/cli.c – line 2749 */
sqlite3_prepare_v2(cfg->db,
    "SELECT value FROM config WHERE key = ?", SQL_NUL_TERM, &stmt, NULL);

```

### Writing Values

Writes utilize `INSERT OR REPLACE` to handle both new keys and updates atomically (line 2800):

```c
/* src/cli/cli.c – line 2800 */
sqlite3_prepare_v2(cfg->db,
    "INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", ...);

```

### Deleting Keys

Removal operations use a simple parameterized delete (line 2818):

```c
/* src/cli/cli.c – line 2818 */
sqlite3_prepare_v2(cfg->db,
    "DELETE FROM config WHERE key = ?", SQL_NUL_TERM, &stmt, NULL);

```

## Runtime Lifecycle

The configuration store follows a predictable lifecycle during application execution:

1. **Startup** – [`main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/main.c) resolves the cache directory via `cbm_resolve_cache_dir()`, then calls `cbm_config_open()` (lines 746–749) to initialize the database connection and ensure the table exists.
2. **CLI Operations** – The `config` sub-command forwards user requests to `cbm_config_get()`, `cbm_config_set()`, or `cbm_config_delete()`.
3. **Feature Access** – Background services like the auto-watcher query flags via `cbm_config_get_bool()` without needing to know the underlying SQL.
4. **Shutdown** – `cbm_config_close()` finalizes the SQLite connection, ensuring all transactions are committed to disk.

## Practical Code Examples

### Opening the Configuration Store

```c
#include "cli/cli.h"

const char *cache_dir = "/home/user/.cache/codebase-memory-mcp";
cbm_config_t *cfg = cbm_config_open(cache_dir);
if (!cfg) {
    fprintf(stderr, "Failed to open config store\n");
    return 1;
}

```

### Reading a Boolean Flag

```c
bool auto_watch = cbm_config_get_bool(cfg, CBM_CONFIG_AUTO_WATCH, true);
printf("Auto-watch enabled: %s\n", auto_watch ? "true" : "false");

```

### Setting a String Value

```c
if (cbm_config_set(cfg, CBM_CONFIG_UI_LANG, "en") != 0) {
    fprintf(stderr, "Failed to write config\n");
}

```

### Deleting a Configuration Key

```c
if (cbm_config_delete(cfg, "temporary_setting") != 0) {
    fprintf(stderr, "Failed to delete key\n");
}

```

### Closing the Store

```c
cbm_config_close(cfg);

```

## Summary

- **Configuration keys** are simple text-to-text mappings persisted in a single SQLite table named `config`.
- The **`_config.db`** file location defaults to `~/.cache/codebase-memory-mcp/` but is configurable via `CBM_CACHE_DIR`.
- All database access is abstracted through the **`cbm_config_*`** API in [`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c), which caches prepared statements for performance.
- **Type conversion** happens at the API layer, allowing boolean and integer values to be stored safely as strings.
- **Canonical key names** are defined as macros in [`src/cli/cli.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.h) to prevent string duplication errors.

## Frequently Asked Questions

### Where is the `_config.db` SQLite database stored by default?

By default, the database is created at `~/.cache/codebase-memory-mcp/_config.db` on Unix-like systems. You can override this path by setting the **`CBM_CACHE_DIR`** environment variable to a different directory before running the tool.

### What happens if I query a configuration key that does not exist?

The `cbm_config_get()` function returns the **`default_val`** parameter you provide. For example, calling `cbm_config_get(cfg, "missing_key", "fallback")` returns `"fallback"` if the key is absent from the table.

### How are boolean and integer values handled if SQLite only stores TEXT?

All values are preserved as strings in the database. The **convenience helpers** `cbm_config_get_bool()` and `cbm_config_get_int()` parse the TEXT values at runtime, converting `"true"`, `"false"`, or numeric strings into the appropriate C types before returning them to the caller.

### Can multiple processes access `_config.db` simultaneously?

SQLite handles concurrency through file locking. While reads can occur concurrently, writes lock the database file. The `cbm_config_*` API does not implement additional locking beyond SQLite's native mechanism, so heavy concurrent write contention could result in `SQLITE_BUSY` errors.