Repository navigation
Description
Summary
After updating the Claude Code plugin from cortex@cortex-plugins 3.24.1 to
hypermnesia-mcp@cortex-plugins 4.23.1 on Windows 11, ~/.claude/methodology/backend.json
does not exist. Without that marker, scripts/launcher.py injects
DATABASE_URL=postgresql://localhost:5432/cortex, so the advertised zero-config SQLite
backend never engages.
On my machine PostgreSQL happened to exist, so this stayed invisible until PostgreSQL
crashed — at which point Cortex became completely unusable (database_not_connected on
every tool) with no fallback. For a user with no PostgreSQL at all, the same path makes
Cortex non-functional immediately after the update.
There appear to be two distinct defects, one of which is the root cause.
Environment
| OS | Windows 11 Pro 26200 |
| Python | 3.13.4 (C:\Program Files\Python313) |
| Plugin | hypermnesia-mcp@cortex-plugins 4.23.1, git 95f3bec |
| Previous | cortex@cortex-plugins 3.24.1 |
| Install path | Claude Code plugin marketplace update |
| PostgreSQL | 15, portable install, psql on PATH, running at install time |
Defect 1 (root cause) — scripts/setup.py crashes when invoked directly on Windows
install-plugin.sh invokes scripts/setup.py with the bare interpreter:
# sqlite branch
CORTEX_MEMORY_STORE_BACKEND=sqlite "$PY" "$PLUGIN_ROOT/scripts/setup.py" || fail ...
# postgresql branch, Windows
"$PY" "$PLUGIN_ROOT/scripts/setup.py" || fail ...scripts/launcher.py deliberately removes the user site-packages directory from
sys.path before importing anything, with an explicit comment about resolving
pywintypes and its DLL. setup.py invoked directly gets no such protection, so a
package in the user site-packages shadows the vendored deps/ tree.
Reproduced on this machine:
File ".../4.23.1/deps/transformers/audio_utils.py", line 58, in <module>
import torchaudio
File "C:\Users\<user>\AppData\Roaming\Python\Python313\site-packages\torchaudio\_extension\__init__.py", line 33
if not _load_lib("libtorchaudio"):
File ".../4.23.1/deps/torch/_ops.py", line 1518, in load_library
raise OSError(f"Could not load this library: {path}")
OSError: Could not load this library: C:\Users\<user>\AppData\Roaming\Python\Python313\site-packages\torchaudio\lib\libtorchaudio.pyd
The vendored transformers imports the user-site torchaudio, which then loads its
.pyd against the vendored torch. Two incompatible builds, one process.
Running the same work through scripts/launcher.py succeeds, which isolates the cause to
the missing user-site guard rather than to the environment itself.
Defect 2 — the backend decision is lost whenever setup fails
The marker write sits after the fail-guarded setup step:
if [ "$BACKEND" = "sqlite" ]; then
... "$PY" "$PLUGIN_ROOT/scripts/setup.py" || fail "scripts/setup.py failed. ..."
else
... "$PY" "$PLUGIN_ROOT/scripts/setup.py" || fail "scripts/setup.py failed. ..."
fi
# ── marker write happens only if we got here ──
MARKER_WRITTEN=$(... path.write_text(json.dumps({'backend': ...})) ...)fail exits, so any setup failure — Defect 1, a network blip during dependency install,
anything — silently discards a backend decision that was already made correctly in Phase 0.
Nothing downstream can tell "the installer chose SQLite but could not finish" apart from
"the installer never ran", and the two need opposite handling.
backend_marker.py then applies rule 5 (nothing → untouched, engine default auto), and
launcher.py:159 completes the regression:
if (
"DATABASE_URL" not in os.environ
and os.environ.get("CORTEX_MEMORY_STORE_BACKEND", "") != "sqlite"
):
os.environ["DATABASE_URL"] = "postgresql://localhost:5432/cortex"The comment above that block states the intent — "On the SQLite backend the URL is unused,
and injecting a PG default would make every hook attempt (and log) a doomed PG connection" —
which is exactly the outcome when the marker is missing.
Observed result
check_setup (abridged, field names as returned):
{
"ready": false,
"fixes_needed": 2,
"checks": [
{"name": "DATABASE_URL", "ok": true,
"detail": "postgresql://localhost:5432/cortex"},
{"name": "PG connection", "ok": false,
"detail": "ConnectionTimeout: connection timeout expired"},
{"name": "pgvector + pg_trgm extensions", "ok": false,
"detail": "ConnectionTimeout: connection timeout expired"}
]
}memory_stats returns database_not_connected. No SQLite fallback is attempted.
Reproduction
- On Windows, have a Python 3.13 user site-packages containing
torch+torchaudio
(any version differing from the vendored one). - Update the plugin from 3.x to 4.23.1 through the marketplace.
cat ~/.claude/methodology/backend.json→ does not exist.- Call any Cortex tool with PostgreSQL stopped →
database_not_connected.
Step 1 is what I can evidence on my machine. I would expect any user-site/vendored
dependency conflict to reach the same outcome through Defect 2, but I have not tested a
second conflict.
Suggested fixes
Defect 1 — invoke setup through scripts/launcher.py, or apply the same user-site
removal in setup.py. The guard already exists and is already justified in a comment; it
is simply not on this code path.
Defect 2 — write the marker in Phase 0, right after BACKEND is decided and before any
step that can fail. The marker records a decision, not a result, so it does not depend
on setup succeeding. If you would rather it reflect a completed install, add a third state
({"backend": "sqlite", "setup": "incomplete"}) so the launcher can distinguish a failed
install from a missing one instead of silently reaching for PostgreSQL.
Independently, consider making the launcher.py:159 fallback conditional on PostgreSQL
being reachable, or emitting one stderr line when it injects the default. Right now the
single most consequential configuration decision in the system is made silently.
Workaround
printf '{\n "backend": "sqlite"\n}\n' > ~/.claude/methodology/backend.jsonThen restart Claude Code. doctor immediately stops probing PostgreSQL and reports the
SQLite store. Note that any pre-existing PostgreSQL memories are not carried over —
there is no PG → SQLite migration path in the repo, which may be worth a separate issue.
Activity
fix(installer): restore prior backend marker when a switch fails
fix(setup): persist backend marker before setup.py can fail; close ML…
fix(sqlite): install sqlite-vec on every backend, self-heal vectorles…
ci(windows): target the Windows leg at what actually broke on Windows (…
chore(release): prepare Cortex 4.23.5 (#641)
docs(install): say where the engine names "auto" and where it falls t…