fix(daemon): let a shipped build relocate the rendezvous via CBM_RUNTIME_DIR - #1645
Merged
Conversation
…IME_DIR
The daemon/CLI rendezvous directory is created under %LOCALAPPDATA% (Windows) or
/tmp -- /private/tmp on macOS -- and every ancestor of it must pass the
private-directory walk. That ancestry is not always acceptable, and when it is
not, EVERY invocation fails, `config list` included, so the settings surface
cannot be reached either:
codebase-memory-mcp: secure daemon endpoint could not be created
#1623 narrowed the Windows side of this by admitting AppContainer package and
capability SIDs on ancestors, and named the remainder explicitly: a live local
group, Authenticated Users inherited from a secondary volume root, and orphaned
unresolvable SIDs still refuse, and "those need CBM_RUNTIME_DIR or a separate
change". #1621 is the POSIX shape of the same dead end -- /private/tmp/cbm-daemon-<uid>
refused with no way to move it.
There was no way to move it in a shipped build. The only relocation hook,
CBM_TEST_DAEMON_RUNTIME_PARENT, is compiled out unless CBM_ENABLE_TEST_SEAMS is
defined, so a test build started while the shipped build did not; CBM_CACHE_DIR
is no help either, because it moves the cache and never the rendezvous.
CBM_RUNTIME_DIR names the parent directory the rendezvous is created under. It
does NOT relax the check: the directory it names goes through exactly the same
validation as the default -- ancestors owned by you or root, not world-writable,
no allow-ACL; the rendezvous directory itself still forced to owner-only -- and a
value that fails is refused rather than silently replaced by the default. The
operator only chooses an ancestry that passes. cbm_safe_getenv never truncates,
so no half of an over-long value can become a runtime parent.
The override is resolved in cbm_daemon_bootstrap_endpoint_new(), the one function
every product endpoint goes through: the daemon, the MCP client, the local CLI,
the index worker, and the install/update/uninstall activation path in cli.c. No
call site can silently keep the default, and the detached daemon inherits the
value with the rest of its environment. An explicit parent still wins, so the
compile-time test seam and the lifecycle guards' isolated namespace behave
exactly as before.
Approach and variable name from #1576 by Leonardo trindade miranda, resolved one
layer lower so the activation path is covered too.
Refs #1574
Refs #1621
Co-Authored-By: Leonardo trindade miranda <tmonestudio@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This was referenced Aug 14, 2026
This was referenced Aug 15, 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.
The blocker
The daemon/CLI rendezvous directory is created under
%LOCALAPPDATA%(Windows) or/tmp—/private/tmpon macOS — and every ancestor of it must pass theprivate-directory walk. When that ancestry is not acceptable, every invocation
fails,
config listincluded, so the settings surface cannot be reached either:There was no way to move it in a shipped build. The only relocation hook,
CBM_TEST_DAEMON_RUNTIME_PARENT, is compiled out unlessCBM_ENABLE_TEST_SEAMSisdefined, so a test build starts while the product build does not.
CBM_CACHE_DIRis no help: it moves the cache and never the rendezvous.
#1623 narrowed the Windows side by admitting AppContainer package and capability SIDs
on ancestors, and named the remainder explicitly — a live local group,
Authenticated Usersinherited from a secondary volume root, and orphaned unresolvable SIDs stillrefuse, and "those need
CBM_RUNTIME_DIRor a separate change". This is that change.#1621 is the POSIX shape of the same dead end.
What this does
CBM_RUNTIME_DIRnames the parent directory the rendezvous is created under(
cbm-daemon-<uid>on POSIX,cbm-daemon-<key>on Windows).It does not relax the check. The directory it names goes through exactly the same
validation as the default — ancestors owned by you or root, not world-writable, no
allow-ACL; the rendezvous directory itself still forced to owner-only — and a value
that fails is refused rather than silently replaced by the default. The operator
only chooses an ancestry that passes.
cbm_safe_getenvnever truncates, so no half ofan over-long value can become a runtime parent.
The override is resolved in
cbm_daemon_bootstrap_endpoint_new(), the one functionevery product endpoint goes through — daemon, MCP client, local CLI, index worker, and
the install/update/uninstall activation path in
src/cli/cli.c. Resolving it thererather than at the call sites is deliberate: no call site can silently keep the
default, and it is outside every
CBM_ENABLE_TEST_SEAMSguard, so the shipped buildhas it. An explicit parent still wins, so the compile-time test seam and the lifecycle
guards' isolated namespace behave exactly as before. The detached daemon inherits the
value with the rest of its environment.
Credit: approach and variable name from #1576 by @tmonestudio, resolved one layer
lower so the activation path is covered too —
Co-Authored-Byin the commit.Test
daemon_bootstrap_runtime_dir_env_relocates_rendezvousintests/test_daemon_bootstrap.c(registered inSUITE(daemon_bootstrap)) asserts threethings in one process: the rendezvous lands under the named directory; an explicit
parent still beats the environment; and a named parent that cannot pass validation
yields no endpoint rather than a quiet fall back to the default. It restores the
environment before asserting, so a failed assertion cannot leak
CBM_RUNTIME_DIRintolater suites in the same process.
Revert-check — production hunk reverted, test rebuilt and rerun:
With the fix in place:
25 passedfordaemon_bootstrap, and 490 passed / 0 failed/ 0 skipped across
daemon_bootstrap daemon_ipc daemon cli daemon_runtime daemon_application daemon_frontend daemon_version activation_transaction.make -f Makefile.cbm lint-ciclean.What is NOT verified here
Local verification was macOS only. Much of the Windows path —
win_parent_valid,win_private_directory_tree_secure, the DACL walk this is meant to route around — iscompiled out on macOS, so a clean local build proves nothing about it. What the local
run does prove is that the override reaches
cbm_daemon_ipc_endpoint_new(), which isthe same parameter the Windows implementation already substitutes for
win_default_runtime_parent(). The Windows behaviour needs the CI leg, and ideally aconfirmation from a reporter on an affected machine.
Still open in #1574 (not in this PR)
Two of the reporter's three asks are unaddressed on purpose, to keep this to one claim:
verifying this I found the existing detail names the wrong component:
private_directory_tree_open()runsposix_directory_parent_secure(current_fd)onthe parent but formats the message with
component, the child about to beentered. So
/private/tmp/cbm-daemon-501: ancestor 'cbm-daemon-501' is not a usable private-directory parent(CBM_CACHE_DIR ignored; daemon still requires /private/tmp/cbm-daemon-<uid> on macOS #1621) actually means/private/tmpfailed, and…/.cache/codebase-memory-mcp: ancestor '.cache' …(No such xattr: com.apple.quarantine #1537) actually means/Users/<user>failed. Both reporters were pointed at a directory that was notthe one refusing. That is a one-line diagnostic fix but a separate claim.
docs/CONFIGURATION.mdsection, which states the rule the walk applies; theWindows-specific DACL shape is not spelled out.
Self-healing a stale runtime directory already exists and needed no change:
private_directory_tree_open()fchmods the final component to0700and clears itsextended ACL when the owner is the current user, and
daemon_ipc_posix_private_directory_hardens_existing_cache_rootalready covers it.