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

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 at line 2716, the initialization logic executes:

/* 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 and declared in 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 (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 at lines 306–309:

/* 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 at lines 6178 and 6272, the MCP server queries feature flags:

/* 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:

/* 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):

/* 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):

/* 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 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

#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

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

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

Deleting a Configuration Key

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

Closing the Store

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, 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →