fix: restore main to green after #1723 — memory-core linter and worker-scope Step 5f - #2257
Merged
Merged
Conversation
…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>
This was referenced Sep 20, 2026
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>
This was referenced Sep 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restores
mainto a green memory-core linter. Until this lands,lint / lintis red on every PR andpr.ymlskips the whole test stage behind it.What broke
#1723 (merged as 92abefa) added an early-return path to
handle_index_repositoryand, following the function's convention, freed its three argument strings there with rawfree(). 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_freeThe 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 whererepo_pathis still NULL passes NULL. On the cross-repo branchmode_strwas freed beforehandle_cross_repo_modeand 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.py→passed: 3806 raw sites across 85 files, none grew-Werror, ASan/UBSanmcp cli daemon_application→ 692 passed, 4 skipped, 0 failed, includingindex_repository_honors_allowed_root, which runs through a replaced early returnSecond 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.shStep 5f: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 onlyrepo_path+mode. #1723's branch never contained that script, so the two never ran together until they met onmain.The worker is right; the script now sends what a supervisor sends — both limits
off, the default, the same objecttests/test_index_policy.cuses. The fail-closed case carries it as well, so a request withoutrepo_pathis still refused for its missing scope, not by accident for a missing policy. No assertion changed.ok: index worker is scoped to the admitted request, not the daemon environment, rc=0.test-windows-guards, istest_daemon_stability.pysection_cold_storm("secure CLI coordination could not be created (endpoint)") — a known nondeterministic guard, unrelated to either commit.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.