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
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
# Changelog

## [Unreleased]

### Fixed
- **Installer trampolines blocked by Windows Defender ASR** —
``install.ps1`` and ``install.sh`` invoked the
``truememory-mcp --setup`` and ``truememory-ingest install`` console-
script shims directly. Those shims are setuptools / uv trampolines with
a per-install unique SHA-256, which Microsoft Defender's Attack-Surface-
Reduction rule
``01443614-cd74-433a-b99e-2ecdc07bfc25`` ("Block executable files from
running unless they meet a prevalence, age, or trusted list criteria")
silently kills at ``CreateProcess`` time on hardened-dev-box
configurations: the binary has zero machines worth of MS cloud
prevalence so the launch is blocked before any user code runs. (The
rule defaults to Audit-only per DISA STIG and CIS Benchmark, but a
growing share of Windows-11 hardened-baseline images run it in Block
mode.) Switched both installers to invoke ``$toolPython -m
truememory.mcp_server --setup`` / ``$toolPython -m
truememory.ingest.cli install`` — the routing through the PSF-signed
high-prevalence ``python.exe`` is invisible to ASR. Same mechanic the
``mcp_server._setup_claude`` writer already uses when it registers the
MCP server with Claude Code / Claude Desktop, so the installer is now
consistent with the runtime config it produces. Added a Windows-ASR
troubleshooting section to the installer's "done" banner and to the
README so users on the rare *Block*-mode hosts can re-run the
equivalent module form manually if upgrading.
- **``_setup_claude`` auto-migrates stale shim paths in Claude config** —
users who had a prior install where the Claude Code / Claude Desktop
MCP server entry was registered with a bare ``truememory-mcp`` shim
path now get a one-time migration on the next ``--setup``: the
existing entry is detected as a setuptools console-script shim, the
registration is removed and re-added with the
``[python_path, "-m", "truememory.mcp_server"]`` form. The previous
"existing config preserved" branch kept those stale shim paths in
place, which on ASR Block-mode Windows hosts meant every Claude
Desktop launch tried to spawn the blocked binary. Migration is
detected by suffix (``/truememory-mcp.exe`` or ``/truememory-mcp``)
and by canonical install-dir substrings (``/scripts/truememory-mcp``,
``/bin/truememory-mcp``) so it works regardless of OS or install
method. Reported as "migrated from shim to python -m form" in the
setup output so the user can see what changed.
- **Installer no longer aborts when tool venv python is unresolvable** —
the early ``Die`` introduced alongside the ASR fix made
``TRUEMEMORY_SKIP_SETUP=1`` unusable: if the uv-tool venv layout
didn't match the expected ``Scripts/python.exe`` /
``bin/python`` path, the installer aborted before honouring the skip.
Softened to a ``Warn`` that skips steps 4 and 5 with an actionable
re-run hint, matching the original installer's behaviour for the
model-download step.
- **install.sh missing ``set -o pipefail``** — partial mid-pipeline
failures previously returned 0 and let the installer print "Installed
successfully" on a broken install. Added.
- **install.ps1 ``$PKG_SPEC`` unquoted** — paths with spaces in
``TRUEMEMORY_SOURCE`` were split into multiple arguments by
PowerShell's parser. Quoted.
- **install.ps1 ``uv tool uninstall`` exit code unchecked** — a real
uninstall failure (locked files, permission issue) was silently
swallowed and the subsequent install masked it. Now warns on exit
> 1 (exit 1 just means "not installed" and is expected on fresh
boxes).
- **README Step 4 Windows tray-quit instruction** — Windows users
closing all Claude windows but leaving Claude Desktop running in the
system tray would never see the MCP config reload, since the config
only loads at a full process launch. Updated to direct users to
right-click the tray icon and Quit.
- **README BibTeX citation version stale** — bumped from ``0.6.0`` to
``0.6.8`` to match ``pyproject.toml``.

## [0.6.8] — 2026-05-11

### Fixed
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ ${\color{#1a73e8}\textbf{\textsf{Step 3.}}}$ Wait 3-5 minutes for installation.

 

${\color{#1a73e8}\textbf{\textsf{Step 4.}}}$ Quit Claude completely and reopen it (Mac: `Cmd+Q`, Windows: close all Claude windows).
${\color{#1a73e8}\textbf{\textsf{Step 4.}}}$ Quit Claude completely and reopen it (Mac: `Cmd+Q`, Windows: right-click the Claude icon in the system tray → **Quit** — clicking X only minimizes it, the MCP config only loads at a full launch).

 

Expand All @@ -82,6 +82,19 @@ That's it. TrueMemory remembers your conversations automatically from here.

Installs [uv](https://docs.astral.sh/uv/) (Astral's Python tool manager) if needed, fetches a managed Python 3.12, installs TrueMemory with all tier models into an isolated tool environment, registers the MCP server, wires up lifecycle hooks, and merges instructions into `~/.claude/CLAUDE.md`. Your system Python is never touched. No sudo, no venvs, no pip struggle.

#### Windows: hardened ASR baselines

If you're on a Windows host that has Microsoft Defender's Attack-Surface-Reduction rule [01443614-cd74-433a-b99e-2ecdc07bfc25](https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-reference#block-executable-files-from-running-unless-they-meet-a-prevalence-age-or-trusted-list-criterion) set to **Block** (rather than the default Audit), the `truememory-mcp.exe` and `truememory-ingest.exe` shims may be silently killed at launch — they're setuptools/uv trampolines with a per-install unique hash, so they fail the cloud-prevalence check.

The installer routes around this by invoking the module form (`python -m truememory.mcp_server`) through the signed `python.exe` wrapper. If you ever need to re-run setup manually on a Block-mode host:

```powershell
python -m truememory.mcp_server --setup
python -m truememory.ingest.cli install
```

This is the same form the installer writes into Claude Code's MCP config, so the running server is unaffected — only the bare-shim invocations are.

#### Audit the script

It's ~200 lines of shell, no sudo, stays entirely under `$HOME`:
Expand Down Expand Up @@ -359,7 +372,7 @@ Find me on X [@Building_Josh](https://x.com/Building_Josh) · Follow us [@Sauron
organization = {Sauron},
year = {2026},
url = {https://github.com/buildingjoshbetter/TrueMemory},
version = {0.6.0}
version = {0.6.8}
}
```

Expand Down
60 changes: 52 additions & 8 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,14 @@ if ($LASTEXITCODE -ne 0) {

# ---------- step 3: install truememory as a uv tool ----------
Say "installing $PKG_SPEC (~3-5 min on first run, downloads all tier models)..."
& uv tool uninstall truememory *> $null
& uv tool install --python $TRUEMEMORY_PY --force --refresh $PKG_SPEC > $null
# uninstall: exit 1 just means "not currently installed" — that's the
# common case on a fresh box. Anything higher is a real problem (locked
# file, permissions) and worth surfacing.
& uv tool uninstall truememory 2>$null *> $null
if ($LASTEXITCODE -gt 1) {
Warn "uv tool uninstall returned $LASTEXITCODE — proceeding with install, but the result may be partial (try closing any running truememory-mcp processes)"
}
& uv tool install --python $TRUEMEMORY_PY --force --refresh "$PKG_SPEC" > $null
if ($LASTEXITCODE -ne 0) {
Die "truememory install failed"
}
Expand All @@ -107,29 +113,60 @@ if ($uvToolDir) {
}
}

# Resolve the tool venv's python.exe up front. We invoke `python.exe -m
# <module>` for every subsequent step instead of the bare `truememory-mcp`
# / `truememory-ingest` console-script shims.
#
# Why: those `.exe` shims are setuptools/uv trampolines with a unique
# per-install hash. Windows Defender's ASR rule
# 01443614-cd74-433a-b99e-2ecdc07bfc25 ("Block executable files from
# running unless they meet a prevalence, age, or trusted list criteria")
# silently blocks them at launch on hardened-dev-box configurations
# because the cloud-prevalence check fails. Routing through `python.exe`
# (signed by the PSF / Astral Python distribution) bypasses the check —
# python.exe is high-prevalence and trusted.
#
# Missing-toolPython is a `Warn`, not a `Die`: the user may have set
# TRUEMEMORY_SKIP_SETUP=1 expecting to configure Claude themselves, and
# even when not, we'd rather finish the install + tell them the exact
# manual command than abort halfway through.
#
# See: https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-reference
$toolPython = $null
if ($uvToolDir) {
$candidate = Join-Path $uvToolDir "truememory\Scripts\python.exe"
if (Test-Path $candidate) {
$toolPython = $candidate
}
}

# ---------- step 4: auto-configure Claude ----------
if ($env:TRUEMEMORY_SKIP_SETUP -eq "1") {
Say "skipping Claude setup (TRUEMEMORY_SKIP_SETUP=1)"
} elseif (-not $toolPython) {
Warn "could not locate the truememory tool venv python at $uvToolDir\truememory\Scripts\python.exe — skipping Claude setup."
Warn "Re-run manually after restarting your terminal:"
Warn " python -m truememory.mcp_server --setup"
Warn " python -m truememory.ingest.cli install"
} else {
Say "configuring Claude Code / Claude Desktop..."
& truememory-mcp --setup
& $toolPython -m truememory.mcp_server --setup
if ($LASTEXITCODE -ne 0) {
Warn "auto-setup returned non-zero (you can re-run it with: truememory-mcp --setup)"
Warn "auto-setup returned non-zero (you can re-run it with: python -m truememory.mcp_server --setup)"
}

Say "installing hooks and CLAUDE.md instructions..."
& truememory-ingest install
& $toolPython -m truememory.ingest.cli install
if ($LASTEXITCODE -ne 0) {
Warn "hook install returned non-zero (you can re-run it with: truememory-ingest install)"
Warn "hook install returned non-zero (you can re-run it with: python -m truememory.ingest.cli install)"
}
}

# ---------- step 5: pre-download models for all tiers ----------
Say "pre-downloading models for all tiers (Edge + Base + Pro)..."
Say " this takes 2-5 min but means tier switching just works afterward."

$toolPython = Join-Path (& uv tool dir 2>$null) "truememory\Scripts\python.exe"
if (Test-Path $toolPython) {
if ($toolPython) {
Say " [1/3] Edge reranker (MiniLM-L-6-v2, ~22MB)..."
& $toolPython -c "from sentence_transformers import CrossEncoder; CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')"
if ($LASTEXITCODE -eq 0) { Ok " [1/3] Edge reranker ready" }
Expand Down Expand Up @@ -188,3 +225,10 @@ Write-Host ""
Write-Host " Note:" -ForegroundColor Yellow -NoNewline
Write-Host " If commands are not found, close and reopen PowerShell."
Write-Host ""
Write-Host " If Windows Defender blocks ``truememory-mcp.exe`` / ``truememory-ingest.exe``" -ForegroundColor Yellow
Write-Host " with 'Block executable files from running unless they meet a prevalence," -ForegroundColor Yellow
Write-Host " age, or trusted list criteria' (ASR rule 01443614), use the module form" -ForegroundColor Yellow
Write-Host " instead — the python.exe wrapper is signed and passes ASR:" -ForegroundColor Yellow
Write-Host " python -m truememory.mcp_server --setup " -NoNewline; Write-Host "# re-run Claude auto-config" -ForegroundColor DarkGray
Write-Host " python -m truememory.ingest.cli install " -NoNewline; Write-Host "# re-install hooks" -ForegroundColor DarkGray
Write-Host ""
45 changes: 37 additions & 8 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ die() { warn "error: $*"; exit 1; }

# ---------- main ----------
main() {
set -eu
# pipefail catches mid-pipeline failures that `set -e` alone misses
# (e.g. a network drop inside `curl ... | sh` where curl returns 0
# but sh aborts partway). Bash-only but the script already uses
# `$(...)` and `||` constructs that assume a modern shell.
set -euo pipefail

TRUEMEMORY_PY="${TRUEMEMORY_PY:-3.12}"
TRUEMEMORY_EXTRAS="${TRUEMEMORY_EXTRAS:-}"
Expand Down Expand Up @@ -113,19 +117,45 @@ main() {
say "adding uv's tool dir to your shell rc (reversible)..."
uv tool update-shell >/dev/null 2>&1 || true

# Resolve the tool venv's python up front so steps 4 and 5 can invoke
# `python -m truememory.mcp_server` / `python -m truememory.ingest.cli`
# directly instead of the `truememory-mcp` / `truememory-ingest` console-
# script shims.
#
# Why: on Windows, those `.exe` shims are setuptools/uv trampolines with
# a unique per-install hash, which Microsoft Defender's ASR rule
# 01443614 ("Block executable files from running unless they meet a
# prevalence, age, or trusted list criteria") blocks on hardened-dev-box
# configurations. We keep the install.sh path consistent with install.ps1
# so a single canonical invocation works across all platforms — even
# though POSIX doesn't have ASR, the shim is the only difference and
# it's a brittle dependency on $PATH resolution timing.
#
# Missing TOOL_PYTHON is a `warn`, not a `die`: the user may have set
# TRUEMEMORY_SKIP_SETUP=1 and skipped the Claude-config step entirely,
# in which case dying here would block the model-download step they
# likely still want.
TOOL_PYTHON="$(uv tool dir)/truememory/bin/python"

# ---------- step 4: auto-configure Claude ----------
if [ "${TRUEMEMORY_SKIP_SETUP:-}" = "1" ]; then
say "skipping Claude setup (TRUEMEMORY_SKIP_SETUP=1)"
elif [ ! -x "$TOOL_PYTHON" ]; then
warn "could not locate tool venv python at $TOOL_PYTHON — skipping Claude setup."
warn "Re-run manually after opening a new terminal:"
warn " python -m truememory.mcp_server --setup"
warn " python -m truememory.ingest.cli install"
else
say "configuring Claude Code / Claude Desktop..."
# truememory-mcp lives at ~/.local/bin/truememory-mcp. Its sys.executable
# resolves to the isolated tool venv, so Claude gets a stable absolute path.
truememory-mcp --setup || \
warn "auto-setup returned non-zero (you can re-run it with: truememory-mcp --setup)"
# Invoke via `python -m truememory.mcp_server` — see comment above for
# the Windows ASR rationale. The module's `if __name__ == '__main__'`
# block routes `--setup` through the same code path as the shim.
"$TOOL_PYTHON" -m truememory.mcp_server --setup || \
warn "auto-setup returned non-zero (you can re-run it with: python -m truememory.mcp_server --setup)"

say "installing hooks and CLAUDE.md instructions..."
truememory-ingest install || \
warn "hook install returned non-zero (you can re-run it with: truememory-ingest install)"
"$TOOL_PYTHON" -m truememory.ingest.cli install || \
warn "hook install returned non-zero (you can re-run it with: python -m truememory.ingest.cli install)"
fi

# ---------- step 5: pre-download models for all tiers ----------
Expand All @@ -135,7 +165,6 @@ main() {
# Use the tool's Python to run the download inside the uv venv.
# stderr is NOT suppressed — HuggingFace's tqdm progress bars show
# download percentage, speed, and ETA, which is better UX than silence.
TOOL_PYTHON="$(uv tool dir)/truememory/bin/python"
if [ -x "$TOOL_PYTHON" ]; then
# Edge: Model2Vec embedder (usually bundled) + MiniLM reranker
say " [1/3] Edge reranker (MiniLM-L-6-v2, ~22MB)..."
Expand Down
54 changes: 48 additions & 6 deletions truememory/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,30 @@ def _path_exists(p: str) -> bool:
except Exception:
return False

def _is_shim_path(cmd: str) -> bool:
"""Return True if ``cmd`` is a setuptools / uv console-script shim
for ``truememory-mcp``.

Why we care: those shims have a per-install unique SHA-256 hash
and Windows Defender's ASR rule 01443614 ("Block executable files
from running unless they meet a prevalence, age, or trusted list
criteria") silently kills them at launch on hardened Win11
baselines. The canonical workaround is to invoke the same code
through the high-prevalence signed ``python.exe`` instead. When
``--setup`` runs on a machine that already had a shim path baked
into the Claude config from a previous install, we migrate it
rather than preserve it.
"""
if not cmd:
return False
lower = cmd.lower().replace("\\", "/")
return (
lower.endswith("/truememory-mcp.exe")
or lower.endswith("/truememory-mcp")
or "/scripts/truememory-mcp" in lower
or "/bin/truememory-mcp" in lower
)

# --- Claude Code CLI ---
claude_bin = shutil.which("claude")
if claude_bin:
Expand Down Expand Up @@ -1323,17 +1347,30 @@ def _path_exists(p: str) -> bool:
existing_cmd = tokens[0]
break

if _path_exists(existing_cmd):
# Working entry — preserve it (don't clobber a dev venv).
if _is_shim_path(existing_cmd):
# ASR-vulnerable shim path baked in by a previous install
# — migrate to `python -m truememory.mcp_server`.
_run_claude([claude_bin, "mcp", "remove", "--scope", "user", "truememory"])
retry = _run_claude(add_cmd)
if retry is not None and retry.returncode == 0:
configured.append("Claude Code (migrated from shim to python -m form)")
elif retry is not None:
print(f" Claude Code: migration failed — {retry.stderr.strip()}", file=sys.stderr)
elif _path_exists(existing_cmd):
# Working entry pointing at a real file — preserve it
# (don't clobber a dev venv).
configured.append("Claude Code (existing config preserved)")
else:
# Stale entry — remove and re-add.
# Empty / unparseable / stale entry — remove and re-add.
# Treating empty as stale (rather than preserve) is the
# safer default: a parse miss + preserve would leave a
# broken entry in place with no diagnostic.
_run_claude([claude_bin, "mcp", "remove", "--scope", "user", "truememory"])
retry = _run_claude(add_cmd)
if retry is not None and retry.returncode == 0:
configured.append("Claude Code (stale entry replaced)")
elif retry is not None:
print(f" Claude Code: update failed — {retry.stderr.strip()}")
print(f" Claude Code: update failed — {retry.stderr.strip()}", file=sys.stderr)
else:
print(f" Claude Code: failed — {result.stderr.strip()}")

Expand All @@ -1359,16 +1396,21 @@ def _path_exists(p: str) -> bool:
servers["truememory"] = {"command": python_path, "args": list(mcp_args)}
desktop_config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
configured.append("Claude Desktop")
elif _is_shim_path(existing_cmd):
# ASR-vulnerable shim path — migrate to `python -m`.
servers["truememory"] = {"command": python_path, "args": list(mcp_args)}
desktop_config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
configured.append("Claude Desktop (migrated from shim to python -m form)")
elif _path_exists(existing_cmd):
# Working entry — preserve it.
# Working entry pointing at a real file — preserve it.
configured.append("Claude Desktop (existing config preserved)")
else:
# Stale entry — replace it.
servers["truememory"] = {"command": python_path, "args": list(mcp_args)}
desktop_config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
configured.append("Claude Desktop (stale entry replaced)")
except Exception as e:
print(f" Claude Desktop: failed — {e}")
print(f" Claude Desktop: failed — {e}", file=sys.stderr)

# --- Report ---
print()
Expand Down