Skip to content

fix: restore main to green after #1723 — memory-core linter and worker-scope Step 5f - #2257

Merged
DeusData merged 2 commits into
mainfrom
fix/mcp-index-args-cleanup-helper
Sep 21, 2026
Merged

DeusData merged 2 commits into
mainfrom
fix/mcp-index-args-cleanup-helper

Conversation

@DeusData

@DeusData DeusData commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Restores main to a green memory-core linter. Until this lands, lint / lint is red on every PR and pr.yml skips the whole test stage behind it.

What broke

src/mcp/mcp.c: grew by 3 (814 -> 817)

#1723 (merged as 92abefa) added an early-return path to handle_index_repository and, following the function's convention, freed its three argument strings there with raw free(). Its CI lint was green on 2026-09-09; on 2026-09-19 the memwaste merge cut this file's raw sites and ratcheted the baseline down to 814. By the time #1723 landed, its green had been measured against a rule that no longer existed.

The merge-result verification that let it through was incomplete — it built and ran five suites and did not run the linter. It does now.

Why the fix is not cbm_free

The three strings are raw heap memory from cbm_mcp_get_string_arg, freed raw by every other path in the function. Routing three of them through the core would free raw memory through the accounting layer — a real bug in place of a linter complaint.

The actual defect: the function had 41 raw free() calls of the same three arguments across a dozen early returns, and #1723 added one more block in the same shape.

The fix

One helper, index_args_free(repo_path, mode_str, name_override), replaces twelve of those blocks. free(NULL) is a no-op, so the one path where repo_path is still NULL passes NULL. On the cross-repo branch mode_str was freed before handle_cross_repo_mode and the other two after; it is neither passed to nor read by that call, so releasing all three after is not observable. Five sites on the normal-completion path are deliberately left alone — they release the strings at different points as ownership is handed to the pipeline.

Raw sites: 817 → 785. The baseline follows, 814 → 785, as the linter's own output asks. It can only tighten.

Verification

  • python3 scripts/lint-memory-core.pypassed: 3806 raw sites across 85 files, none grew
  • Build clean under -Werror, ASan/UBSan
  • mcp cli daemon_application692 passed, 4 skipped, 0 failed, including index_repository_honors_allowed_root, which runs through a replaced early return

Second commit: the test stage was red too, hidden behind the lint failure

Once lint went green here the test stage ran for the first time since #1723 merged, and failed on every platform at scripts/test.sh Step 5f:

=== Step 5f: worker request-scope regression ===
worker did not index the admitted repository
{"content":[{"type":"text","text":"missing or incomplete trusted worker policy"}], ... "isError":true}

Same kind of collision as the linter one. #1723 made an index worker refuse a request without _cbm_index_policy (the supervisor resolves the policy and sends it; a worker never reads it from config or environment). tests/test_worker_session_scope.sh (a08b9ec, 2026-09-06) plays the supervisor by hand and sent only repo_path + mode. #1723's branch never contained that script, so the two never ran together until they met on main.

The worker is right; the script now sends what a supervisor sends — both limits off, the default, the same object tests/test_index_policy.c uses. The fail-closed case carries it as well, so a request without repo_path is still refused for its missing scope, not by accident for a missing policy. No assertion changed.

  • RED locally (production binary from this branch, script unmodified): identical output to CI, rc=1.
  • GREEN: ok: index worker is scoped to the admitted request, not the daemon environment, rc=0.
  • On the failed run, 12 of the 13 red test jobs carry exactly this signature and nothing else. The 13th, test-windows-guards, is test_daemon_stability.py section_cold_storm ("secure CLI coordination could not be created (endpoint)") — a known nondeterministic guard, unrelated to either commit.
  • Step 6, which Step 5f kept CI from reaching, passes locally (security-strings allow-list 4/4, destructive-ordering contract PASS).

Process note. Two reds from one merge, both invisible to a textually clean merge and to a green that predated the code it collided with. The merge-result check now runs the linter and scripts/test.sh's process-level steps, not only the unit suites.

…rings

main fails the memory-core linter:

    src/mcp/mcp.c: grew by 3 (814 -> 817)

PR #1723 (merged as 92abefa) added a new early-return path to
handle_index_repository and, following the function's convention, freed
its three argument strings there with raw free(). Its CI lint was green
on 2026-09-09. On 2026-09-19 the memwaste merge cut this file's raw sites
and ratcheted the baseline down to 814, so by the time #1723 landed its
green was measured against a rule that no longer existed. The linter is
right and the merge-result check that let it through was incomplete: it
built and ran the suites and did not run the linter. It does now.

The fix is not cbm_free. The three strings come from
cbm_mcp_get_string_arg and resolved_repo_path_from_project_arg and are
raw heap memory, released raw by every other path in the function;
routing three of them through the core would free raw memory through the
accounting layer. The actual defect is that the function had 41 raw
free() calls of the same three arguments across a dozen early returns,
and #1723 added one more block in the same shape.

One helper, index_args_free(repo_path, mode_str, name_override), now
replaces twelve of those blocks. free(NULL) is a no-op, so the one path
where repo_path is still NULL passes NULL. On the cross-repo branch
mode_str used to be freed before handle_cross_repo_mode and the other
two after it; mode_str is neither passed to nor read by that call, so
releasing all three after it is not observable. Five sites on the
normal-completion path are left as they were: they free the strings at
different points as ownership is handed to the pipeline, and collapsing
them would change that order for no gain.

Raw sites in this file: 817 -> 785. The baseline follows the improvement,
814 -> 785, as the linter asks; it can only tighten.

Build: clean, -Werror, ASan/UBSan. mcp cli daemon_application:
692 passed, 4 skipped, 0 failed -- index_repository_honors_allowed_root,
which runs through a replaced early return, included.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
main fails scripts/test.sh at Step 5f on every platform:

    === Step 5f: worker request-scope regression ===
    worker did not index the admitted repository
    {"content":[{"type":"text","text":"missing or incomplete trusted
    worker policy"}], ... "isError":true}

Second collision from the #1723 merge (92abefa), and the same kind as
the first. #1723 made an index worker refuse any request that does not
carry `_cbm_index_policy`: a worker never resolves the resource policy
from config or environment, the supervisor does and sends it along.
tests/test_worker_session_scope.sh, added on 2026-09-06 by a08b9ec,
plays the supervisor by hand -- it runs `cli --index-worker
index_repository '{"repo_path":...,"mode":"fast"}'` directly -- and
#1723's branch never contained that script, so its CI never ran the two
together. The merge was textually clean and the result was wrong. It
stayed hidden behind the lint failure the previous commit fixes, because
the test stage only runs once lint is green.

The worker is right and the script was incomplete: it now sends what the
supervisor sends, both limits off, which is the default
(`"index_max_files":"off","index_max_source_mb":"off"`, the same object
tests/test_index_policy.c uses). The fail-closed case carries it too, so
that a request without repo_path is still refused for its missing scope
("request workspace scope invalid") and not, by accident, for a missing
policy. No assertion changed.

RED, production binary built from this branch, script unmodified:
    worker did not index the admitted repository
    ... "missing or incomplete trusted worker policy" ...      rc=1
GREEN:
    ok: index worker is scoped to the admitted request, not the daemon
    environment                                                  rc=0

On the failed CI run of this PR, 12 of the 13 red test jobs carry exactly
that signature and nothing else. The 13th, test-windows-guards, is
test_daemon_stability.py section_cold_storm ("secure CLI coordination
could not be created (endpoint)", a racing cold start) -- a known
nondeterministic guard, unrelated to either commit here. Step 6, which
Step 5f's failure kept CI from reaching, passes locally:
security-strings allow-list 4 passed, destructive-ordering contract PASS.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData DeusData changed the title fix(mcp): one release point for handle_index_repository's argument strings fix: restore main to green after #1723 — memory-core linter and worker-scope Step 5f Sep 20, 2026
@DeusData
DeusData merged commit def38f3 into main Sep 21, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant