Repository navigation
Description
Summary
On Windows, install.ps1 fails at the "activation" step with activation could not reserve exclusive access; no activation was committed. — regardless of which directory is used as --dir, and independent of the CBM_CACHE_DIR location. This looks like the same "ancestor walk" ownership/ACL check discussed in #1830 (Linux/Docker userns-remap) and #1533 (Windows containers), but triggered here on a completely standard, non-containerized Windows 10 install by Windows' own out-of-the-box default ACLs.
Environment
- OS: Windows 10 Pro, build 10.0.19043 (21H1) — no TLS 1.3 support in schannel
- codebase-memory-mcp: v0.10.8 (windows-amd64)
- Running fully elevated as a local Administrator (confirmed
SeCreateGlobalPrivilegeandSeDebugPrivilegeenabled,IsInRole(Administrator) = True) - No antivirus/EDR installed (
WinDefendservice absent,SecurityCenter2empty, no known AV process running)
Repro
irm https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.ps1 -OutFile install.ps1
# (had to patch line 11 to `Tls12` only -- see "Separate issue" below)
.\install.ps1 --dir=C:\Users\<user>\codebase-memory-mcpOutput:
Downloading codebase-memory-mcp-windows-amd64.zip...
Checksum verified.
Extracting...
Verified candidate: codebase-memory-mcp 0.10.8
error: activation could not reserve exclusive access; no activation was committed.
error: this is NOT a running-session problem — the reservation itself failed (coordination lock, leftover state, or permissions). ...
This reproduces identically:
- with a brand-new, freshly created
--dir(clean ACL: owner+SYSTEM+Administrators+the account only, no other principals) - with a brand-new
CBM_CACHE_DIRpointed elsewhere (rules out leftover cache state) - across three separate target directories
Root cause (confirmed via Process Monitor)
I captured a full ProcMon trace across a failing run. Every single Windows API call the process makes succeeds (CreateFile, the 296 MB staged-candidate write, QuerySecurityFile, etc.) — there is no ACCESS DENIED, SHARING VIOLATION, or any other OS-level denial anywhere in the trace. The process itself deliberately deletes its own staged candidate (SetDispositionInformationEx with FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS | FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK) and exits 1 right after inspecting the DACL of the target directory and its parent. This is an application-level decision, not an OS failure — i.e. the same class of check as posix_directory_parent_secure in #1830, just walking the Windows ACL ancestor chain instead of POSIX uid ownership.
I isolated two concrete triggers of the same check family, both on default, unmodified Windows ACLs:
--dir=C:\<fresh-dir>(parent = user profile root, e.g.C:\Users\Administrator): fails with the generic message above. The profile root carries Windows' standard(A;;0x1200a9;;;WD)ACE — Everyone/World, read-only, not inherited by children — which is present on essentially every Windows user profile by default.--dir=C:\<fresh-dir-directly-under-C>: fails with a specific error instead:error: failed to stage install candidate: activation transaction I/O failed: acl-grants-cross-account-mutation to S-1-5-11 (os 0) at C:/<dir>S-1-5-11is Authenticated Users — the account that getscreate file/create subfolderrights onC:\by default on every Windows install since Vista ((A;OICIID;0x1301bf;;;AU)inherited from the drive root). This is not a hardened/misconfigured machine; it's Microsoft's stock default.
Since (1) and (2) fire on stock Windows defaults with no containerization, no third-party ACL tooling, and no other logged-in principals actually able to write anything of consequence, the ancestor-walk check appears to be unconditionally too strict for Windows: any non-owner principal anywhere in the ancestor chain — even a read-only Everyone ACE, even the standard AU-create-only ACE on C:\ — trips it. This would make normal (non-Server-Core, non-freshly-re-ACL'd) Windows installs unable to complete activation at all.
Separate/related issue: TLS 1.3
install.ps1 line 11 hardcodes:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13On Windows 10 (schannel has no TLS 1.3 support before Windows 11 / Server 2022), setting this bitmask makes HttpClientHandler's GetAsync fail outright with WebException: İstek durduruldu: SSL/TLS güvenli kanalı oluşturulamadı. (SSL/TLS secure channel could not be created), even though the same machine's Invoke-WebRequest to the identical URL succeeds fine. Dropping the -bor Tls13 fixes it. Might be worth an OS-version check (or a try/fallback) before setting Tls13 in the bitmask, since combining an unsupported protocol flag with a supported one causes a hard failure rather than a graceful fallback here.
What I've ruled out
- Leftover lock/cache state (tested 3 different cache paths, all fresh/empty)
- File permissions on the leaf target directory (confirmed writable, full control)
- AV/EDR interference (none installed on this machine)
- Restricted/UAC-filtered token (fully elevated,
SeCreateGlobalPrivilege+SeDebugPrivilegeenabled) - Port conflicts on 9749 (nothing listening)
--dry-run(works fine — it's specifically the write/activation path that fails)
Suggested fixes
- Treat read-only ACEs (no write/delete/take-ownership bits) on ancestor directories as non-disqualifying — mirroring the fix direction discussed in Linux: ancestor walk fails unconditionally in Docker userns-remap containers (single-uid mapping,
/owned by nobody/65534) #1830 for the POSIX overflow-UID case. - Special-case the OS-default ACEs that ship on every Windows install (
Authenticated Userscreate-only onC:\,Everyone/Usersread-only on profile roots) rather than treating any non-owner ACE as a cross-account mutation risk. - Consider exposing a
CBM_SKIP_ANCESTOR_WALK-style escape hatch (as proposed in Linux: ancestor walk fails unconditionally in Docker userns-remap containers (single-uid mapping,/owned by nobody/65534) #1830) for Windows too. I checked:v0.10.8's binary does not contain that string at all (dumped allCBM_[A-Z_]+identifiers from the executable — 44 env vars total, e.g.CBM_ALLOWED_ROOT,CBM_CACHE_DIR,CBM_RUNTIME_DIR,CBM_DIAGNOSTICS; no ancestor/skip-walk override among them), and setting it had no effect on the repro above. So the escape hatch discussed in Linux: ancestor walk fails unconditionally in Docker userns-remap containers (single-uid mapping,/owned by nobody/65534) #1830 isn't implemented yet, at least not for Windows/this version. - For
install.ps1: gate theTls13flag on[Environment]::OSVersion.Version(or catch-and-retry withTls12only) so it doesn't hard-fail on Windows 10.
Happy to provide the full ProcMon .pml/CSV or run further diagnostics if useful.
Activity
fix(install): trust the local machine's built-in Administrator; name …
Merge pull request #2188 from DeusData/fix/1856-win10-tls-and-stock-a…
fix(install): stop naming TLS 1.3 where schannel cannot negotiate it
fix(cli): name the check that refused, and a remedy that can remove it
DeusData commentedon Sep 12, 2026
Thank you for this — the ProcMon trace, the decoded
S-1-5-11, and the TLS bisect are the reason all of this was actionable rather than guesswork. Three separate things were in your report, and they landed differently, so here is each one honestly.1. The TLS 1.3 failure is fixed —
aacf96a2You were exactly right.
install.ps1setTls12 -bor Tls13unconditionally. Windows 10's schannel has no TLS 1.3 (it arrives with Windows 11 / Server 2022, build 20348), but .NET Framework 4.8 still defines the enum value — so the assignment succeeds, nothing warns, and the first request dies with "Could not create SSL/TLS secure channel". An unsupported flag in that bitmask is a hard failure, not a downgrade.The protocol set is now computed:
Tls12, plusTls13only on build 20348+, with an enum-name probe so the script still parses on .NET 4.7. Verified on a Windows box with the shipped parser — on Windows 11 it keepsTls12, Tls13; forcing build 19045 through the same expression yieldsTls12alone.2. The refusal now names the check that refused —
73490637This is the part we owe you an apology for. Your output was:
…and there was nothing above. That was our bug, not a gap in your report: the code path that validates the cache, rendezvous and log directories printed that message bare, bypassing the helper that appends the detail naming the failing component. So the binary computed the answer you needed and then discarded it before printing. You could not have diagnosed it from the outside, and no amount of ProcMon would have shown it.
Fixed, plus a contract test that guards the whole class — any future code path that prints a refusal without attribution now fails CI.
3. The
C:\<dir>case refuses on purpose — but now tells you how to fix itYour analysis of the stock
(A;OICIID;0x1301bf;;;AU)ACE was correct, and we looked hard at simply tolerating it. We decided not to, and it is worth explaining why rather than just saying no.That mask includes
FILE_ADD_FILEandDELETEon the directory. The installed binary itself is safe regardless — it gets an owner-only,SE_DACL_PROTECTEDDACL — but the directory is not the binary.install.ps1copies itself there andcbm updatetells you to run that copy; the install directory goes onto yourPATH; and the application directory is first in the DLL search order. Tolerating that ACE would let any authenticated account on the machine plant a DLL beside the executable or edit the updater we tell you to run.So it still refuses — but the message no longer sends you down a dead end. It now says whether the grant was inherited, because the remedy differs and the wrong one silently does nothing:
icacls <dir> /remove:g <sid>cannot remove an inherited ACE. For your case the working command is:Installing under the default
%LOCALAPPDATA%\Programs\codebase-memory-mcpavoids it entirely, since the profile already isolates the ACLs.What would help us: one re-run
Case 1 — the failure with
--dirunder your profile root — is the one we still cannot explain. YourC:\Users\<user>\...directory had clean ACLs, so it should not have tripped the ACL check at all, and until now the error told us nothing. With the fix above it will name the exact check that refused.If you have the time once v0.10.9 ships, could you re-run that original repro and paste the new error? That one line should be enough to finish this. Labelling
awaiting-reporteronly to track that the ball is with you — not a nudge.Thanks again for the depth here; it turned into three fixes and one design decision we had to think properly about.
github-actions commentedon Sep 12, 2026
Thanks for the report! To move this forward we need a bit more so we can reproduce it ourselves:
codebase-memory-mcp --versionyou're onOnce that's here we'll pick it straight back up. Heads-up: issues left
awaiting-reporterare automatically closed after a few weeks of silence, but a comment reopens the door anytime.zaferavci1 commentedon Sep 14, 2026
Thanks for the detailed breakdown and the fixes! I'll keep an eye out for the v0.10.9 release, re-run the repro under my user profile (C:\Users<user>...), and post the new error log here as soon as it's out.
fix(install): trust the local machine's built-in Administrator; name …
fix(install): stop naming TLS 1.3 where schannel cannot negotiate it
fix(cli): name the check that refused, and a remedy that can remove it
fix(install): trust the local machine's built-in Administrator; name …
fix(install): stop naming TLS 1.3 where schannel cannot negotiate it
fix(cli): name the check that refused, and a remedy that can remove it
fix(install.ps1): reserve temp paths exclusively instead of adopting …
fix(install.ps1): reserve temp paths exclusively instead of adopting …