Skip to content
Open
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
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1028,12 +1028,16 @@ codebase-memory-mcp config set index_max_files 250000 # optional per-index so
codebase-memory-mcp config set index_max_source_mb 16384 # optional per-index source-size limit
codebase-memory-mcp config set index_max_rss_mb 8192 # optional worker-tree charged-memory limit
codebase-memory-mcp config set index_max_duration_seconds 3600 # optional per-request duration
codebase-memory-mcp config set index_cache_max_mb 32768 # optional projected cache-size limit
codebase-memory-mcp config set index_min_free_disk_mb 4096 # optional free-space reserve
codebase-memory-mcp config reset auto_index # reset to default
```

The four `index_max_*` settings default to `off`. Exceeding one fails the complete
index attempt rather than publishing a partial graph; an existing serving index
is preserved. See [Index resource limits](docs/INDEX_RESOURCE_LIMITS.md).
The six index resource settings default to `off`. Exceeding one fails the
complete index attempt rather than publishing a partial graph; an existing
serving index is preserved. Storage probes also fail closed when an enabled
measurement cannot be completed. See
[Index resource limits](docs/INDEX_RESOURCE_LIMITS.md).

### Environment Variables

Expand Down
17 changes: 10 additions & 7 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Current keys:
| `index_max_source_mb` | `off` | Optional maximum accepted source size in MiB in one discovery run. |
| `index_max_rss_mb` | `off` | Optional maximum charged memory in MiB for the complete contained index-worker process tree (`64..1048576`). On macOS this is phys_footprint, not resident size. |
| `index_max_duration_seconds` | `off` | Optional maximum wall-clock duration in seconds for the whole index request, including crash/hang recovery (`1..86400`). Like `index_max_rss_mb`, it stops a worker only before it starts to publish the new index; see [INDEX_RESOURCE_LIMITS.md](INDEX_RESOURCE_LIMITS.md). |
| `index_cache_max_mb` | `off` | Optional maximum projected cache size in MiB after publication. |
| `index_min_free_disk_mb` | `off` | Optional minimum free MiB reserved on the cache filesystem while indexing. |
| `install_hooks` | `true` | Install CBM-managed client hooks. `install --no-hooks` persists `false`; `install --hooks` restores `true`. |

Bare installs and updates respect the stored hook preference. MCP registration, skills, instructions, and profiles remain installed when hooks are disabled.
Expand Down Expand Up @@ -141,13 +143,14 @@ has not run yet).
> `auto_index` still runs, and `index_repository` stays available for manual
> reindexing.

The four `index_max_*` settings are independent and disabled by default. They
apply to explicit indexing, automatic indexing, and watcher re-indexing, but not
to `cross-repo-intelligence`, which does not scan repository source files.
Equality is allowed; exceeding any setting fails the complete index request and
preserves any previously serving database. Worker RSS covers descendants and is
not the same as the internal `CBM_MEM_BUDGET_MB` allocation budget. Total
duration is independent of the existing 15-minute no-log-progress timeout. See
The six index resource settings are independent and disabled by default.
They apply to explicit indexing, automatic indexing, and watcher re-indexing,
but not to `cross-repo-intelligence`, which does not scan or publish repository
source indexes. Equality is allowed; exceeding a setting fails the complete
index request and preserves any previously serving database. Worker RSS covers
descendants and is not the same as the internal `CBM_MEM_BUDGET_MB` allocation
budget. Total duration is independent of the existing 15-minute no-log-progress
timeout. See
[Index resource limits](INDEX_RESOURCE_LIMITS.md) for counting, validation, and
error-response details.

Expand Down
29 changes: 27 additions & 2 deletions docs/INDEX_RESOURCE_LIMITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,31 @@ request: it is true only when a servable index existed when the request began,
no attempt of the request (including an earlier one that crashed and was
recovered) claimed publication, and that index still serves.

## Storage settings

| Key | Default | Accepted value | Protects |
|---|---:|---:|---|
| `index_cache_max_mb` | `off` | `off` or `1..1048576` | Projected cache bytes after replacement |
| `index_min_free_disk_mb` | `off` | `off` or `1..1048576` | Free bytes reserved on the cache filesystem |

Set or reset these keys through the same `config set` and `config reset`
commands. With both keys `off`, indexing does not scan the cache tree or probe
filesystem capacity.

Projected cache usage is the current cache size, minus the old project
database and SQLite sidecars only when that generation is confirmed valid and
replaceable, plus the current operation's staging artifacts. Other projects
and unrelated files always count toward the limit and are never evicted.

Free space is checked before staging, after the staged build completes, and
immediately before atomic publication. Equality is allowed. An enabled
measurement that cannot be completed fails closed with
`code: "resource_probe_failed"` and
`stage: "storage"`. A limit breach uses `code: "resource_limit_exceeded"`.
Both cases preserve the old serving database. Staging files created by a
terminated supervised worker are tagged with a private task token and removed
after its process tree is quiescent; cleanup cannot match another attempt.

## Trust and compatibility

Limits are read from the CLI-managed `_config.db`; they are not MCP request
Expand All @@ -129,5 +154,5 @@ parent policy.
These settings do not replace or increase `auto_index_limit`, change the 512 MiB
single-file cap, alter workspace-root authorization, or affect
`cross-repo-intelligence`. With all settings `off`, discovery follows the
existing path and the supervisor performs no periodic RSS probe or total-duration
termination.
existing path, the supervisor performs no periodic RSS probe or total-duration
termination, and the pipeline performs no cache-tree or free-space probe.
3 changes: 2 additions & 1 deletion scripts/test-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ _cbm_test_runtime_daemon() {
# nothing else.
cbm_test_index_worker_policy_json() {
printf '%s' '"_cbm_index_policy":{"index_max_files":"off","index_max_source_mb":"off"'
printf '%s' ',"index_max_rss_mb":"off","index_max_duration_seconds":"off"}'
printf '%s' ',"index_max_rss_mb":"off","index_max_duration_seconds":"off"'
printf '%s' ',"index_cache_max_mb":"off","index_min_free_disk_mb":"off"}'
}

cbm_test_runtime_cleanup() {
Expand Down
3 changes: 3 additions & 0 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -8105,6 +8105,9 @@ static const config_key_def_t CONFIG_KEYS[] = {
{CBM_INDEX_CONFIG_MAX_RSS_MB, "off", "Max worker process-tree charged memory MiB, or off"},
{CBM_INDEX_CONFIG_MAX_DURATION_SECONDS, "off",
"Max wall-clock seconds for the whole index request, or off"},
{CBM_INDEX_CONFIG_CACHE_MAX_MB, "off", "Max projected cache MiB after publish, or off"},
{CBM_INDEX_CONFIG_MIN_FREE_DISK_MB, "off",
"Minimum free cache-filesystem MiB during indexing, or off"},
};

/* #1558: ui_enabled and ui_port were reachable ONLY by hand-editing
Expand Down
16 changes: 10 additions & 6 deletions src/daemon/application.c
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,12 @@ static int application_worker_start_default(void *context, const char *args_json
(void)context;
cbm_index_resource_policy_t resource_policy;
char error[CBM_SZ_256] = {0};
char task_db_path[CBM_SZ_1K];
if (!cbm_mcp_index_policy_from_internal_args(args_json, &resource_policy, error,
sizeof(error))) {
cbm_log_error("daemon.index.policy", "error", error);
sizeof(error)) ||
!cbm_mcp_index_task_db_path(args_json, task_db_path, sizeof(task_db_path))) {
cbm_log_error("daemon.index.worker_policy", "error",
error[0] ? error : "could not resolve worker index path");
*worker_out = NULL;
return -1;
}
Expand All @@ -403,9 +406,10 @@ static int application_worker_start_default(void *context, const char *args_json
const char *root = yyjson_get_str(yyjson_obj_get(yyjson_doc_get_root(args_doc), "repo_path"));
worker->project = application_index_project_key(root, args_json);
yyjson_doc_free(args_doc);
int result = cbm_index_worker_start_with_policy_log(
args_json, memory_budget_bytes, &resource_policy, false, marker_file, quarantine_file,
application_worker_diagnostic, worker, duration_origin_ms, &worker->handle);
int result = cbm_index_worker_start_with_storage_policy(
args_json, memory_budget_bytes, &resource_policy, task_db_path, false, marker_file,
quarantine_file, application_worker_diagnostic, worker, duration_origin_ms,
&worker->handle);
if (result != 0) {
application_worker_destroy_default(NULL, worker);
*worker_out = NULL;
Expand Down Expand Up @@ -1559,7 +1563,7 @@ static void application_job_request_facts_begin(cbm_daemon_application_job_t *jo
cbm_index_resource_policy_t policy;
char error[CBM_SZ_256] = {0};
if (cbm_mcp_index_policy_from_internal_args(job->args_json, &policy, error, sizeof(error)) &&
cbm_index_policy_worker_enabled(&policy)) {
(cbm_index_policy_worker_enabled(&policy) || policy.max_task_temp_bytes.enabled)) {
cbm_mcp_index_request_facts_begin(job->args_json, &job->request_facts);
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/daemon/bootstrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ static bool bootstrap_worker_budget_valid(const char *text) {
return value > 0;
}

static bool bootstrap_worker_stage_token_valid(const char *token) {
size_t length = token ? strlen(token) : 0;
if (length < 6U || length >= 64U) {
return false;
}
for (size_t index = 0; index < length; index++) {
unsigned char ch = (unsigned char)token[index];
if (!((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
ch == '-' || ch == '_')) {
return false;
}
}
return true;
}

/* Keep the bootstrap role boundary exact and fail closed before any client or
* worker state is initialized. index_supervisor owns the matching builder and
* performs the captured-build comparison after this syntax-only classification. */
Expand Down Expand Up @@ -147,6 +162,12 @@ static bool bootstrap_worker_argv_exact(int argc, char *const argv[]) {
}
next += 2;
}
if (next < argc && bootstrap_arg_is(argv[next], "--index-worker-stage-token")) {
if (next + 1 >= argc || !bootstrap_worker_stage_token_valid(argv[next + 1])) {
return false;
}
next += 2;
}
return next == argc;
}

Expand Down
Loading
Loading