Repository navigation
Releases
v0.12.1
v0.12.1
Every install path works again, and Helm charts join the graph.
v0.12.1 fixes v0.12.0's package installs and the other install paths we found broken while checking every channel. It also links Helm charts to what they deploy, and stops the graph from inventing a registry for container images.
Upgrading? The first index of each existing project after the upgrade is a full rebuild, once (image identities changed; see below).
Fixed: installs
- npm, PyPI and Go installs.
npm i,pnpm add,pip installand the Go wrapper could not install v0.12.0 on any platform. Each wrapper refused an archive member above 256 MiB, and every v0.12.0 binary is 331-335 MB. The size limits are gone from all three wrappers. The archive is still verified against the release'schecksums.txtbefore it is unpacked, its contents must be exactly the expected files, and the binary must answer--version. Thank you, @hydroplexus, for the precise report (#2657). - Homebrew. The formula was still at 0.10.3, and on Linux it installed the dynamically linked build, which needs glibc 2.38 (Ubuntu 22.04, Debian 12 and RHEL 9 do not have it). It now installs the current release, and on Linux the static
-portablebuild that runs on any distribution. go install. A pinnedgo install .../pkg/go/cmd/[email protected]never worked, and@latestinstalled a stale version. Releases now carrypkg/go/vX.Y.Ztags, and the wrapper installs the release matching its own version.- npm
next. Thenextdist-tag no longer points to an old release candidate; a stable release moves it. - Docs. winget and Chocolatey were listed as install channels but are not published; they are removed until they are. Windows arm64 builds ship and are now listed.
Install-path smoke tests. Every pull request and every release now installs the npm, PyPI and Go wrappers, the way a user's package manager does, against the real release archives, and runs the binary each one installed. A release is published only after these installs pass, so an installer that cannot install what we ship can no longer reach you.
Helm charts in the graph
A Helm chart used to be an isolated node. Now it links to what it deploys (#2663):
- Chart → objects (
DEPLOYS,via: chart): every Kubernetes object rendered from the chart'stemplates/(a subchart's objects belong to the subchart). - values file → Chart (
CONFIGURES):values.yamlandvalues-<x>.yamlbesideChart.yaml. - Chart → images (
DEPLOYS,ref: image): every image its objects name literally (a templated image names none).
HelmRelease → Chart → Deployment → Image is now one traversable path. On 20 infrastructure repositories, charts with no outgoing edge dropped from 238 of 260 to 109, and 2,511 chart-to-object links appeared; every other edge stayed identical.
Images keep the registry the code names
An image without a registry host used to be stored under an invented docker.io/library/... (#2654). Docker Hub is only Docker's default: Podman and CRI-O resolve short names through a search list, mirrors rewrite them, and a local build tag may exist on no registry at all. An image now keeps its name as written, with registry: unknown when no host is stated; a stated host is kept (Docker Hub's spellings become docker.io). FROM nginx and image: docker.io/library/nginx are now two Image nodes. This changes image identities, which is why each project is rebuilt once after the upgrade.
Upgrade notes
- The first index of each existing project after the upgrade is a full rebuild, once.
- Image nodes without a stated registry now carry
registry: unknownand the name as written, instead ofdocker.io/library/....
Security Verification
Every binary published below was scanned by VirusTotal before smoke and soak testing, and the verdict for the exact shipped bytes is linked per product (decisive engines: 61–69).
Every shipped binary was clean.
| Product | Shipped binary | VirusTotal verdict |
|---|---|---|
linux-amd64 |
6761236d038de87bf49da5695dd52d7928180d814624d4ceb8d8e849ba07f4c8 |
clean |
linux-arm64 |
43a096f3655a9ea0606146da1dbd92aece344e24248e305fc007d239e3b891ae |
clean |
linux-amd64-portable |
77745491f0a9c346d6da23eed4d92b5feaa31a27a883c9faa4fa9f47c8183705 |
clean |
linux-arm64-portable |
6a64e20e2ca8dc15b4a412e981e5aea60804ed3e952d693ab491c93ccd36ed45 |
clean |
darwin-amd64 |
f3ba4d7dd462be868eff85f0dccb7375405f58084da800826065df2312b028ab |
clean |
darwin-arm64 |
a36cfa1f5d10d8c4762501d63feef082711aae4704b9e8f9066db52779a8601e |
clean |
windows-amd64 |
39f6a789cd858d7428561ac5afaefb9a47b018dcba0ad6fa41ae71a45e0ed656 |
clean |
windows-arm64 |
b19816bb88ef3145a0f4b81856665c67a684cc038e06ae696e76960277c67b41 |
clean |
Selection is tuple-local and defaults to stripped. The selected executable SHA-256 was verified again after packaging; archive containers were not redundantly submitted to VirusTotal. Their hashes remain available in checksums.txt.
Durable evidence: candidate provenance, candidate VirusTotal results, selection decisions.
v0.12.0
v0.12.0
Meet CBM Lens, and a code graph that reads your documentation.
v0.12.0 is the biggest release of codebase-memory-mcp yet: 231 merged pull requests since v0.11.0, 40 of them from the community.
Four headlines define it.
-
CBM Lens Beta: the code graph, for humans. Until now the graph's power reached people only through their agents. Lens is our new human interface. It brings the code knowledge graph to you directly. It ships inside the binary you already run and listens on loopback only. It puts source, the graph, the architecture, index coverage and a browser-local assistant side by side, and every fact it shows says where it comes from. Lens is built on the work of @BernhardJackiewicz, who now joins the project to own it.
-
Documentation joins the graph. Until now the graph stopped where the code stopped. Now doc comments, Markdown, reStructuredText, AsciiDoc and PDF documents, architecture decision records, diagrams and OpenAPI specs are part of it. Each is linked to the functions, types and files it talks about. An agent can now traverse your documentation and land on the exact code a page describes, and walk from a function back to every page that explains it. So can you, in Lens. Linking is strict by design: a reference becomes an edge only when it names exactly one definition. Link families that have not yet cleared their accuracy bar are counted and reported, never guessed into the graph.
-
Smart CI: the graph picks the tests.
detect_changescan now say which tests a change can affect. It seeds the graph from the diff, walks everything that depends on the changed code, and names the tests at the end of those paths, each with its reason. When the evidence is missing or doubtful, it answers "run everything" and says why; it never narrows silently. codebase-memory-mcp's own CI is the first user: pull requests run only the test lanes their change touches, and the graph's test selection runs beside every PR. -
Infrastructure as code in the graph. Kubernetes manifests, Helm charts, Flux and Argo CD, Compose files, Dockerfiles and the scripts, Makefiles and CI jobs that deploy them now link up. A Service can be followed through its workload, its image and its Dockerfile to the program the container runs.
The code graph itself got sharper too:
- C typedefs, macros and enumerators get identities of their own.
- Process launches become
SPAWNSedges. - Resolution fixes cover C#, C++, TypeScript, Python, PHP, Rust and Go.
Upgrading? The first index of each existing project after the upgrade is a full rebuild, once. Details are in Upgrade notes. A few identities and defaults also changed; see Breaking changes.
Welcome, @BernhardJackiewicz
@BernhardJackiewicz joins codebase-memory-mcp as the subsystem owner for the UI. @BernhardJackiewicz built the reading interface on the CBM read surface that became CBM Lens, and contributed it to the project (#2068; design and tracking in #1964). He also rewrote the README's Lens section with new walkthroughs and screenshots (#2383). From this release on he owns the UI subsystem and takes care of Lens. We are very glad to have him on board.
Highlights
- CBM Lens Beta, an interface for people. It explores a project by the file you read or the code you mark, by graph neighbourhood, and by architecture, routes and hotspots. It also explains index coverage, edits the project's architecture decision record, and answers questions about your selection with a model that runs in your browser. It is built into the binary:
codebase-memory-mcp --ui=true(#2068). - Documentation joins the graph.
- Doc-comment references become
MENTIONSedges from the documented definition to the code they name. Nine languages: C#, Go, Java, Kotlin, Rust, Perl, TypeScript/JavaScript, PHP and C (#2550, #2593). - Markdown, reStructuredText/Sphinx, AsciiDoc/Antora and PDF documents link their sections to the code they reference (#2551).
- To list every section that names a definition, run
trace_pathinbound overMENTIONS.
- Doc-comment references become
- Architecture decisions become nodes. ADR files become
ADRnodes carrying their status, date, deciders and the decision itself. A record that says "Supersedes ADR-3" gets aSUPERSEDESedge (#2551). - Diagrams and API specs enter the graph.
- Mermaid, PlantUML, Graphviz, D2, draw.io and Excalidraw diagrams become
DiagramandDiagramElementnodes joined byDIAGRAM_ARROWedges, whether embedded in a document or standing on their own. - Each OpenAPI/Swagger operation becomes a
Routenode with the same identity as the route your code registers (#2593).
- Mermaid, PlantUML, Graphviz, D2, draw.io and Excalidraw diagrams become
- Smart CI, driven by the graph.
- From Service to code.
trace_pathinrunmode walks Service → workload → Image → Dockerfile → program, together with the container's environment. Adeploymode follows what deploys what, and atopologymode what references what (#2597). - A sharper code graph.
- C and C++ get typedef nodes, distinct macro and enumerator identities, and member access resolved through the object's type (#2543, #2458).
- A definition written once per
#ifbranch,#[cfg]or Go build tag is now one node; C/C++ platform files (src/unix/,src/win/,arch/x86/, ...) keep their own path-named nodes, related by a sharedvariant_keythat Smart CI follows (#2458, #2630). - Process launches become
SPAWNSedges in about 30 languages (#2458). - Doc comments are captured whole: Go full-doc capture rose from 35% to 99.999% and C# from 10% to 99.8% (#2459).
- Long indexes survive client deadlines.
- Observed vs. possible.
ingest_tracesnow really stores the runtime call paths you send it. Lens marks the hops that actually ran (#2068).
CBM Lens Beta (#2068)
The MCP tools are the agent's interface to the graph. CBM Lens Beta is yours: our attempt to bring the power of the code knowledge graph to humans. It replaces the previous graph UI and is built into the binary:
codebase-memory-mcp --ui=true --port=9749- Explore: follows the current file or marked source, with its direct dependencies and a file-level impact summary from graph and local Git evidence.
- Galaxy: search nodes, files and folders, then expand incoming or outgoing relationships by depth and edge type.
- Architecture: the repository map, routes, hotspots, system structure and behavior.
- Coverage and System: what the index missed and why, plus daemon status, indexes, diagnostics and MCP configuration.
- ADR: edit the project's decision document in the browser, shared with
manage_adr. - Add project index (Alt+P): pick a local repository, name it and follow the daemon's progress.
- Local chat: ask about the code you have selected. Inference runs in your browser. Model weights are not bundled; they download from Hugging Face only when you activate a model.
- Runtime evidence: call paths sent through
ingest_tracesare stored, and Lens marks the hops that actually ran.
Lens runs on loopback, keeps source files read-only, and names where each fact comes from. Frontend errors are journaled on the server, so a bug report can include them.
CBM Lens is built on the work of @BernhardJackiewicz on the CBM read surface, contributed with the author's agreement (design and tracking in #1964). @BernhardJackiewicz now owns the UI subsystem. Thank you, @BernhardJackiewicz. This release would look very different without your work.
Documentation in the graph
Doc-comment links (#2550, #2593)
- C#
<see cref>,<seealso cref>,<exception cref>and<inheritdoc cref>bind by the compiler's own lookup rules, including MSBuild global usings evaluated from your project files (#2550). - Go doc links, Javadoc and KDoc tags (
{@link},@see,@throws,{@value},@sample), Rust intra-doc links, Perl PODL<>, TypeScript/JavaScript{@link}, PHPDoc@seeand Doxygen@ref/@seefollow the same rule (#2593). - Each of the 17 doc-comment link families that ship in #2593 passed a blind audit on repositories never used to build it (bar: Wilson 95% lower bound ≥ 0.90).
- Families still below the bar resolve but stay out of the graph, for example TS/JS
{@linkcode}and@see, diagram links and OpenAPIoperationIdlinks. index_statusgains adoc_linksblock: theMENTIONScount, plus unresolved references counted by reason (missing,ambiguous,external, …). Passdiagnostics='full'for samples.
Documents (#2551)
- Markdown: sections link to the code they name through links, bare paths, and code spans holding paths or qualified names. A line range such as
#L3-L9binds the innermost definition that holds it. A bare name is never an edge. - reStructuredText / Sphinx: a
Sectionper title, with links from roles (:class:,:meth:…), object directives,autodoc,literalincludeand inline literals. Python names resolve the way Sphinx imports them.conf.pyis read as text, never executed. - AsciiDoc / Antora: sections link through
include::directives and tag regions. - PDF: every page becomes a
Sectionwhose text is searchable, linked to the paths, file names and qualified names it mentions. The extractor is dependency-free and built for untrusted input: 44,070 seeded mutant PDFs ran under ASan and UBSan with zero findings. Encrypted documents are rep...
v0.11.0
v0.11.0
A large release, and an unusually community-heavy one: 171 pull requests merged since
v0.10.8 (9 of them dependency bumps), 76 of them from contributors outside the maintainer.
Thank you — this release is mostly your work. It is a minor version rather than another 0.10.x
patch because the index format moves (one rebuild, below) and the tool output contract changes.
The reason v0.11.0 exists is install, uninstall and daemon-startup reliability, and memory: if cbm
ever refused to install, refused to uninstall cleanly, died at startup without telling you why, or
blew through the memory budget on a large repository, this is the release to take.
Two things to know before you upgrade, and one thing you will notice.
Highlights
The first run after upgrading rebuilds your index once
This is expected. It is not an error, and nothing is lost.
PR #1104 (feb72080, by @LA-10) completes the File-node change from #1108: a File node's
qualified name now keeps its file extension, so auth.py and auth.ts in the same directory are
finally distinct nodes instead of colliding on one name. That moves the on-disk index format
boundary, so an index built by v0.10.8 or earlier carries the old, collided identities.
Rather than migrate in place — an old collided database cannot reconstruct the siblings it lost —
cbm stamps the format in PRAGMA user_version and takes a stale-format index through one full
reindex. Your ADRs and index metadata are preserved across it (#516). After that single run, the
stamp is current and incremental indexing resumes as normal.
What you will see: the first index_repository (or the first watcher/daemon-triggered index) after
upgrading takes as long as a cold index of that repository instead of the usual incremental
seconds. A large monorepo will feel it. Everything after that is incremental again.
You may also see one orphan_removed line on that first run — that is #2111's new sweep clearing
staging files left behind by a worker that was killed under an older version. Also harmless, also
once.
Lean, lossless output — findability preserved, no capability removed
PR #1597 (3909e5a0) makes CLI and MCP responses compact by default. To be explicit about what
this is not: no tool was removed, no field you could query before became unreachable, and no
answer got harder to find. What changed is that operational detail moved behind explicit flags and
parameters, and the default response carries the graph answer itself.
Kept in the default response, always:
- graph rows and node identities
- exact totals, not "about N"
- truncation state, so you always know when you are seeing a subset
- continuation cursors for everything that was omitted
Moved behind explicit opt-in: diagnostics, evidence columns, progress chatter, and JSON output.
Two companion changes complete the contract. 14c229b1 adds lossless semantic pagination —
max_output_tokens is enforced as a deterministic, model-neutral byte ceiling that cuts at whole
semantic units instead of slicing bytes mid-record, and every omission is independently pageable.
Cursors are bound to the query arguments, the result state and the index generation, so a stale
cursor fails closed rather than silently paging a different snapshot. 4a9fcd97 adds an explicit
quiet CLI mode for scripting.
Measured on the same serialized MCP tools/list fixture: 23,075 → 15,071 bytes (−34.7%), and
−31.4% / −30.7% on cl100k / o200k token counts. For result tables with repeated paths, response-local
directory-prefix compression cut token-shaped output by 38.3% (Unix paths) and 44.5% (Windows paths);
inputs where compression would not pay are left uncompressed, so no response gets larger.
Invalid UTF-8 in source is now preserved reversibly instead of being replaced, and code search is
scoped to canonically indexed files with globs that stay portable to BusyBox/Alpine.
Memory: the budget is honoured, and the index needs far less of it
PR #2202 moves the indexer's memory onto one allocation core and makes CBM_MEM_BUDGET_MB a
promise rather than a hint. When an index approaches the budget, finished per-file results are
parked on disk and loaded back only for the moment a later pass reads them; the phases after
extraction start from the floor instead of inheriting everything that was extracted. The
semantic pass sizes its working set to the headroom left. What no budget can move is the graph
itself, so a budget below the graph's own size still fails whole, cleanly, as #2112 describes.
Measured on the Linux kernel (8.5 M nodes), same machine as v0.10.8: the default budget
(24,576 MB) used to abort the run; it now completes without spilling. At 16,000 MB and at
15,000 MB it completes through the spill path with the charged memory peaking at 14.6–14.7 GB,
and the graph is identical. On the ten LSP corpora we benchmark, peak memory dropped between
40% and 80% (Go 17.2 → 3.9 GB, Java 27.6 → 11.0 GB, C# 27.0 → 11.2 GB, TypeScript 12.0 → 2.5 GB)
with no corpus slower than v0.10.8: C# indexes in 128 s instead of 463 s, the kernel in 233 s
instead of 295 s.
Three things fell out of the proof runs and ship in the same release:
- A per-file CPU budget covers the whole file, not just the parse. Pathological files (a
23 MB single-expression JIT test in dotnet/runtime, a few generic-nesting stress tests) used to
hold a worker for minutes in the usage walk; they now stop at the budget, keep what was
extracted, and skip the type-aware walks, logged by path. - A crash in cross-file resolution on large C/C++ trees is fixed: the C++ class walk wrote a
per-file scratch signature into the shared type registry, which another worker then read after
the file's memory was gone. Found with an ASan build of the server on dotnet/runtime; the
registry is copy-on-write now, and a test guards the contract. - Locale-free formatting on macOS: every
vsnprintftook the process locale lock, and
eighteen workers formatting type names serialized on it. Each thread formats with a locale of
its own now.
The daemon and the CLI are unchanged in behaviour; a 30-minute soak on all three platforms
holds a flat memory line (Linux 11.6 MB, macOS 17.4 MB, Windows 16.8 MB) under continuous
queries.
Upgrade notes
- One full reindex on first run. See above. Expected, once per index, ADRs preserved.
- A new
<db>.stage.*.locksidecar appears next to the database while an index runs (#2111).
It is the ownership marker that lets cbm tell a live writer from a dead one; it is removed when
the run finishes. - An over-budget index spills first, then fails whole instead of soft-overshooting (#2202,
#2112). Near the budget, finished results are parked on disk under<cache>/spill/for the rest of
the run and the run usually completes at the floor; an index whose graph alone exceeds the budget
stops cleanly withreason:"over_memory_budget"and a namedbudget_mb/peak_rss_mb, and your
previously serving index is left byte-identical. RaiseCBM_MEM_BUDGET_MBif you hit it — the
message names the variable. - No configuration file changes are required.
Install and reliability fixes
Windows 10 could not install at all
#2188 (042a58bb, reported and diagnosed by the reporter of #1856). install.ps1 set
SecurityProtocol = Tls12 -bor Tls13 unconditionally. Windows 10's schannel has no TLS 1.3 — it
arrives with Windows 11 and Server 2022 (build 20348) — but .NET Framework 4.8 still defines the
enum value, so the assignment succeeded silently and the first HTTPS request then died with
The request was aborted: Could not create SSL/TLS secure channel. An unsupported flag in that
bitmask is a hard failure, not a downgrade. Every Windows 10 machine hit this. The protocol set
is now computed: TLS 1.2 always, TLS 1.3 only on build 20348 and later, with an enum-name probe so
the script still parses on .NET Framework 4.7.
The same PR makes an activation refusal actionable. It used to print "Check the errors above" with
nothing above it; it now names the check that refused, says whether the offending ACE was
inherited, and gives a remedy that actually works for that case
(icacls <dir> /inheritance:r /grant:r "%USERNAME%":(OI)(CI)F — /remove:g cannot remove an
inherited ACE, which is why the old advice appeared to succeed while the refusal persisted).
No safety check was relaxed: an install directory writable by other accounts is still refused,
now in terms you can act on.
Elevated and non-standard Windows installs
#2139 (1b067e3b, closes #1705, #2023, #1686). A runtime directory created by an elevated
process is owned by the machine's built-in Administrator account, even when that account is renamed
or disabled — and cbm refused it, so install and activation failed outright on those machines. cbm
now resolves this machine's built-in Administrator SID (via the local SAM account domain) and
trusts it. Deliberately not a trailing-RID-500 match: a domain admin's -500, or another machine's
Administrator, is still foreign and still rejected. Separately, an owner-trust refusal that used to
report a bare status -3, os 0 now names the offending owner's SID alongside the path.
Daemon startup failures now tell you what happened
#2113 (91bee4fb, closes #1828). A daemon that could not start — /tmp full being the
reported case — reported a misleading 30-second "active or starting" timeout and no cause. Every
listener stage now carries stage=, errno= and path= (for example
stage=pending_publication errno=ENOSPC path=/tmp/cbm-daemon-<uid>/…), and a doomed start now
surfaces in about a second as CBM daemon failed to start: <stage> failed with <errno> at <path>
instead of burning the full deadline and then reporting the wrong thing.
#2138 (055fbb7d, closes #1830). Inside a single-uid user namespace
(unshare -U --map-current-user, `podman --user...
v0.10.8
v0.10.8
Note on v0.10.7: this release supersedes v0.10.7, which was lost to a release-pipeline
publishing error (published under the tag0.10.7instead ofv0.10.7; package installers
resolve the v-prefixed tag, and release immutability makes the name unrecoverable). The npm
and PyPI 0.10.7 packages are deprecated/yanked for that reason — their binaries were fine,
their download URLs were not. v0.10.8 carries the identical code content plus the pipeline
hardening that prevents a recurrence.
A community-heavy patch release: 19 pull requests merged since v0.10.6, most from
contributors. Thank you all!
Graph correctness
- Cypher aggregates no longer undercount (#1196). Both truncation mechanisms are fixed:
unlabeled scans stopped collecting candidates at max_rows before aggregation ran
(#1323, @Enferlain), and relationship-expansion buffers were capped so aggregation never
saw rows beyond an internal growth limit (#1698).count()/collect()now see the
complete match set;max_rowslimits only the rows returned, as documented. - Python aliased from-imports resolve to the real definition (#1371, @Joseph-MingEn).
from .gate import execute as bridge_executenow produces a CALLS edge toexecute
itself rather than losing the call, and aliased base-class imports land on the real class
node. On our django benchmark this recovered 26 INHERITS and 42 OVERRIDE edges with zero
losses elsewhere, and left TypeScript graphs byte-identical. - Cross-language suffix matching for qualified-name lookups (#1647, @rudi193-cmd).
- search_graph stops returning semantic-only ghosts — results backed by nothing but a
similarity edge no longer surface as if they were definite matches (#1319, @JJordan0C). - List-valued fields extract correctly instead of collapsing to their first element
(#1325, @Enferlain). - Index responses report persisted coverage truthfully (#1326, @Enferlain): an
incremental run that does not revisit a file with recorded parse gaps no longer reports
those gaps as vanished — the response now composes its skipped/parse-partial summaries
from the stored coverage state, falling back to per-run data only if that read fails.
Windows
- Non-ASCII search content no longer degrades to
?(#1704). The search_code raw scan
pipes PowerShell output, and PowerShell 5.1 encodes that pipe in the console OEM
codepage — so Cyrillic, CJK, or accented content turned into question marks depending on
which console the server inherited. The pipe is now pinned to UTF-8 unconditionally. - search_code understands UTF-8 output paths and gains a context column for matches
(#1253, @anfedoro). - Code search is bounded and cancellable — abandoning a query no longer leaves an
orphaned scan running (#1608, @ertankucukoglu).
Platforms & tools
- FreeBSD support (#1467, @PR9000).
- list_projects pagination: a lean default response plus offset/limit and
include_details for the full view (#1181, @tmonestudio). - Extensionless shebang scripts detect their language (#1203, @ibaldr89).
- Index-mode capability rebuild (#1263, @astandrik).
- is_test detection for tests/ directories (#1308) and a diff-aware clang-tidy
hook (#1310, both @Yyunozor).
Release pipeline (the v0.10.7 lesson, hardened)
- Release gates fail closed on cancelled jobs: a timed-out gate can no longer cascade into
a silently skipped test matrix (#1719). - Malformed version inputs are refused preflight — a bare, non-v-prefixed version can never
again publish an unresolvable tag (#1719).
Internal
- Test harnesses use isolated daemon runtimes so suite runs cannot touch the account
daemon (#1695, @astandrik); LF checkout contracts for shell entrypoints and git hooks
(#1314 @mlandolfi90, #1699 with @xumian520); the stdin-gate regression tests no longer
depend on the registry shipping a zero-argument tool (#1704).
Security Verification
Every binary published below was scanned by VirusTotal before smoke and soak testing, and the verdict for the exact shipped bytes is linked per product (decisive engines: 61–68).
Every shipped binary was clean.
| Product | Shipped binary | VirusTotal verdict |
|---|---|---|
linux-amd64 |
cb6dc545a8cb714799461ba7d8c223dbca7d02ca46ea5a4215c1beb450fd8e09 |
clean |
linux-arm64 |
e861b4ae831a24ab69e82bb9c4ca8c70029af55b9a074ef0ed2565a53c0de24c |
clean |
linux-amd64-portable |
1175645cb30560e7e47d78611cd1bcb509478eaf6d4e51f72fe18327ee9c1351 |
clean |
linux-arm64-portable |
0bf1b3cf3659f6dcdb01bb6313a93dcbcb53e2cd1a52820f024df7a0cf48f44e |
clean |
darwin-amd64 |
1d5b79257d91973809ee70482781db3d1a78f62fc1cd5d18a5ab0608451d16d4 |
clean |
darwin-arm64 |
2412e017268bef8f847f38d1b0f79f63185b38c27fe6fba637067bfc87c0eedf |
clean |
windows-amd64 |
b4b403b1d7c4def3785f148b93f345ce8427858f4f5489ce28580c4387a336a6 |
clean |
windows-arm64 |
67b0341ee62f07f850d3954e4f387855f90ea8c6c4b7ed41b8a62d61344373a4 |
clean |
Selection is tuple-local and defaults to stripped. The selected executable SHA-256 was verified again after packaging; archive containers were not redundantly submitted to VirusTotal. Their hashes remain available in checksums.txt.
Durable evidence: candidate provenance, candidate VirusTotal results, selection decisions.
0.10.7
Warning
Broken for npm/PyPI installs — superseded by v0.10.8. A publishing error released this under the tag 0.10.7 instead of v0.10.7. The npm/PyPI package installers resolve their downloads via the v-prefixed tag, which cannot be created retroactively (release immutability tombstones the name). Direct downloads from this page work normally — the binaries themselves are fine and fully verified. Use v0.10.8 or newer for package-manager installs, or scripts/setup.sh.
What's Changed
- fix(extract): converge Function/Method is_test with the tests/ path filter by @Yyunozor in #1308
- fix(hooks): scope pre-commit clang-tidy to staged changes (#1264) by @Yyunozor in #1310
- fix(mcp): isolate semantic-only JSON search by @JJordan0C in #1319
- fix(search): preserve compound requested fields by @Enferlain in #1325
- fix(cypher): scan all unlabeled query candidates by @Enferlain in #1323
- fix(registry): drop suffix_match CALLS across language boundaries by @rudi193-cmd in #1647
- fix: rebuild index when mode adds capabilities by @astandrik in #1263
- fix(daemon): add native FreeBSD process-image identity and /home log-path alias by @PR9000 in #1467
- fix(mcp): restore scalable project discovery by @tmonestudio in #1181
- fix(discover): detect extensionless scripts by shebang by @ibaldr89 in #1203
- fix: preserve UTF-8 in search_code source by @anfedoro in #1253
- fix(build): force LF line endings for shell scripts by @mlandolfi90 in #1314
- fix: unbreak main — stdin-gate #1359 guards + Windows search_code UTF-8 pipe by @DeusData in #1704
- fix(test): isolate harness daemon runtimes by @astandrik in #1695
- fix(python): resolve aliased from-import CALLS to real def by @Joseph-MingEn in #1371
- fix(cypher): expansion materializes every matched row — the cap falsified aggregates (#1196) by @DeusData in #1698
- fix(mcp): bound and cancel Windows code search by @ertankucukoglu in #1608
- fix(build): LF contract for extensionless git hooks + a line-ending guard by @DeusData in #1699
- fix(index): preserve persisted coverage summaries by @Enferlain in #1326
New Contributors
- @Yyunozor made their first contribution in #1308
- @JJordan0C made their first contribution in #1319
- @Enferlain made their first contribution in #1325
- @rudi193-cmd made their first contribution in #1647
- @ibaldr89 made their first contribution in #1203
- @anfedoro made their first contribution in #1253
- @Joseph-MingEn made their first contribution in #1371
Full Changelog: v0.10.6...0.10.7
Security Verification
Every binary published below was scanned by VirusTotal before smoke and soak testing, and the verdict for the exact shipped bytes is linked per product (decisive engines: 60–68).
Every shipped binary was clean.
| Product | Shipped binary | VirusTotal verdict |
|---|---|---|
linux-amd64 |
9f24d35e42d511c9d5de8f2f3a2c503bfce7e920901f94d0cff837d1371312cd |
clean |
linux-arm64 |
ee517a1b83cda7232bf9f0e2d02807a25207937796bde26dd1dd518be8fb7dd1 |
clean |
linux-amd64-portable |
e9772b28473c11016e59d4dc272b8290deb1efe633c17eb942676f1595b5011e |
clean |
linux-arm64-portable |
a516c044a59c2e8be40366c84aab2a04c2d112298f496f8204f498cbea7883ba |
clean |
darwin-amd64 |
2d5b042b59189645e6975bd3e9772ec760a8368164223fdb85c55a9c67f1d2d2 |
clean |
darwin-arm64 |
24c8b6ee8711e465db359c2ed3ba87c9c350a105f2e77f7d3e4c72b098cd43af |
clean |
windows-amd64 |
2d55ab63c05011a6c00939fc79e17ae0432c4b1fe7675b6dcc86e242a1be437b |
clean |
windows-arm64 |
c5d26ab2e4f59d7e7bc9fbfaa3e08c92127b5ea75c2b7942087edb8a49828fca |
clean |
Selection is tuple-local and defaults to stripped. The selected executable SHA-256 was verified again after packaging; archive containers were not redundantly submitted to VirusTotal. Their hashes remain available in checksums.txt.
Durable evidence: candidate provenance, candidate VirusTotal results, selection decisions.
v0.10.6
codebase-memory-mcp v0.10.6
Two stories in one release: the 0.10.x indexing slowdown is gone, and the install
failures that survived v0.10.5 are fixed — including every construct from the configs you
sent us. If cbm got slower for you since 0.9.0, or install still refused your Hermes,
goose, OpenCode, or Codex config, this is the release to take.
The indexing regression program (#1669)
Since 0.10.0, indexing large repos had drifted from slower to unusable — java 6× worse,
C# 2.6×, TypeScript 2×. The cause was not one bug but a family: per-file work that scaled
with the whole corpus. All of it is fixed, measured on the same host against the same
corpora as the reports:
- Java 477 s → 86 s (elasticsearch): cross-file resolution rebuilt a registry of every
definition in the corpus per file. It is now built once and shared, with a per-file
overlay for local symbols. - C# 1211 s → 450 s (dotnet/runtime), now with 48% more edges than 0.9.0: three
corpus-proportional scans eliminated, plus an import-context bug that had been silently
suppressing C# usage edges — one 147 KB generated file went from 490 s to 6.8 s. - TypeScript 37 s → ~24 s (microsoft/TypeScript): the expression-type evaluator
re-evaluated shared subexpressions once per enclosing alternative — 2^n on tsc-compiled
spread chains. A per-node memo makes it one evaluation per node; the pathological file
from the TS conformance suite dropped from 20 s to effectively zero. - Linux memory blowup fixed (#1654): v0.10.4 could OOM a 36 GB machine indexing a tree
v0.9.0 handled in 13 minutes. mimalloc's Linux arena-commit default is restored. - It stays fixed: a new complexity guard suite runs in CI on every platform and trips
on counter ratios (never wall time) if any pipeline pass goes superlinear again, and
CBM_PROFILE=1now prints per-pass timings from any production binary.
Install: the constructs from your real configs
Hermes (#1631, reported by @iandol and Galaxy-VN, root-caused with an isolated repro by
@rg6304): four pieces of ordinary YAML made install fail permanently — empty flow
collections (plugins: [], tool_choice: {}), block sequences at column 0, double-quoted
strings continued with a trailing \, and mid-word apostrophes (LET'S GO in a persona
string opened a "quote" that never closed). All four are accepted now; quotes and
anchors are indicators only where a value begins, the same rule the * fix used in
v0.10.4. Both reporters' full configs — 15 KB each — install end-to-end with every
original byte preserved.
Entries we wrote ourselves are recognised again. Older cbm versions wrote command:
unquoted; the goose block gained a required field; your entry was then declared "foreign"
forever. An entry under our key that matches a shape any previous release wrote — or
whose command is our binary at a moved path — is repaired in place. Truly foreign entries
are still refused byte-untouched.
goose (#1673 + #1675): the extension block now carries the name field goose's parser
requires — before this, goose silently dropped the entry and the "successful" install was
invisible. envs: {} in a sibling no longer aborts the write (community fix by
@pcristin — a model contribution, merged as-is). Old blocks repair on upgrade.
OpenCode / annotated MCP entries (#1630, #1582): when a client adds keys to our entry
("enabled": true and friends), repairs now replace only the command value — comments and
client keys survive verbatim. And ownership comparison is separator-insensitive: an entry
storing C:\...\codebase-memory-mcp.exe is the same binary as the installer's
C:/.../codebase-memory-mcp.exe. @gotspatel's posted config was the file that cracked
this one after persistence across four releases.
Windows-authored configs with a UTF-8 BOM (#1656): PowerShell's Set-Content writes a
byte-order mark, and every YAML edit failed on such files regardless of content — the
reporter proved content-independence with a 26-byte file, and those 26 bytes were their
23-byte control plus exactly the BOM. It is treated as a prologue now: skipped for
structure, preserved on write.
Codex (#1633, closing @spam0115's residual from #1580): the hook reconciler accepts
the managed hook pair v0.10.2 wrote (including the Windows CRLF+BOM variant) and rewrites
it in place; refusals now name their reason instead of failing generically.
Windows: the DACL stops fighting you
The runtime directory's security descriptor was re-stamped on every process start,
whether or not anything was wrong. #1601 counted eleven no-op "Security change" USN
records against a single file in a day; #1620 lost atomic publishes to the rewrite window.
The re-stamp now fires only when the directory is not already in the exact owner-only
state — and directories damaged by the pre-v0.10.3 DACL regime (the empty-DACL logs
folder @patxisan found, which explained a month of unreadable 0-byte worker logs across
six reports) are detected and repaired in place. (#1416, #1601, #1620)
Also shipped: CBM_RUNTIME_DIR is forwarded in generated Codex configs (#1664 — a
relocated daemon was invisible to Codex-spawned servers), a missing final newline is no
longer reported as a partial parse, and update no longer names an installer that is not
there (#1653).
Credits
@iandol · Galaxy-VN · @rg6304 · @gotspatel · @spam0115 · @pcristin · @Carnival-z ·
@tmonestudio · @patxisan · @roosteer — the configs, ACL dumps, and minimal reproductions
in this release's issues did most of the diagnostic work. Thank you.
Security Verification
Every binary published below was scanned by VirusTotal before smoke and soak testing, and the verdict for the exact shipped bytes is linked per product (decisive engines: 60–68).
Every shipped binary was clean.
| Product | Shipped binary | VirusTotal verdict |
|---|---|---|
linux-amd64 |
749bd2c37c2ad891392e0872b9e1fb0df7432f04c7ca0f49d11d00b24c5f2ec8 |
clean |
linux-arm64 |
e41cba76e58005edd4d21e19cbe1faf2c8f684c56fe78141c9956a9c632c9d26 |
clean |
linux-amd64-portable |
8eea4ec828cd0cfa1c406cda5640a8dfa5381878b4d96b106cdc53aff3733508 |
clean |
linux-arm64-portable |
c783294c009186c1c91c79d3611ef991af4db7816dbad13a922538513af97cac |
clean |
darwin-amd64 |
34851da132369455b35530879c13e95360429251ab5e9604bc301beb25c9bcc5 |
clean |
darwin-arm64 |
8fdbdb2ec81f9526b34e71dffd46589b0e06fd5d60773732fd256c684d0251f4 |
clean |
windows-amd64 |
3099e91c29d8507c94bfb7ed431a1f89459ad9afdcea413cbab23c9ec3cfd972 |
clean |
windows-arm64 |
f5f0585308d7f6130386857e1747a338abe194cb9b082c37d81acf43b8f18e9d |
clean |
Selection is tuple-local and defaults to stripped. The selected executable SHA-256 was verified again after packaging; archive containers were not redundantly submitted to VirusTotal. Their hashes remain available in checksums.txt.
Durable evidence: candidate provenance, candidate VirusTotal results, selection decisions.
v0.10.5
codebase-memory-mcp v0.10.5
An install-and-startup release. If v0.10.4 refused to install for you, refused to write your
config, or told you something that turned out not to be true, this is the one to take.
Several of these are cases where cbm refused its own work — an installer directory our own
binary then rejected, a config entry we wrote ourselves and later called foreign, an error
message that named the wrong directory and sent people to inspect a file that was fine.
Windows: installs that refused themselves
The installer was creating a directory our own binary then rejected. install.ps1 made its
staging directory with New-Item, which inherits whatever %TEMP% carries. The downloaded binary
validates its own directory and refuses inherited cross-account mutation grants — so we handed our
own check a directory we had just made wrong. Measured on a Windows VM: a fresh staging directory
came up unprotected with five inherited ACEs; it now carries a single owner-only entry.
That is the acl-grants-cross-account-mutation to S-1-5-21-… failure. Reported by @RoccoZero,
@aecesr, @Kiborgik and @nasodaengineer, whose corrections stopped us fixing the wrong
thing twice — @Kiborgik warned that keying on the sandbox group's name would miss a synthesized-SID
variant, and @nasodaengineer disproved an off-by-one-RID theory by showing the ACE really was present
on the inherited DACL. @spam0115 independently wrote a working installer whose decisive line was
the same icacls /inheritance:r, and handed it back to us rather than keeping it.
Sandboxed applications no longer block the daemon. The ancestor walk refused any
mutation-granting entry on %LOCALAPPDATA%, including AppContainer identities left by ordinary
installed software. On reported machines the blocking entry resolved to a shipping desktop
application — nothing a user could reasonably remove. Ancestor components now tolerate AppContainer
package and capability identities; the private runtime directory keeps demanding the exact current
user, unchanged. Approach and the ancestor-only boundary follow @mlandolfi90's work.
Startup: when the daemon could not be reached
-
The rendezvous directory can be relocated in a shipped build via
CBM_RUNTIME_DIR. Previously
the only relocation hook was compiled out unless test seams were enabled — so a test build
started where the product build did not, and when the default ancestry was unacceptable
every invocation failed,config listincluded, leaving no way to reach the settings surface
and fix it. (#1537, #1574, #1621)@tmonestudio and @Carnival-z each proposed exactly this, independently, before we built it
— @tmonestudio in #1574 with the ancestry walked and the offending component named, @Carnival-z in
#1621 after finding thatCBM_CACHE_DIRmoves the cache but never the rendezvous. Both had opened
pull requests reaching the same conclusion this change implements; the merged commit credits
@tmonestudio by name for the approach and the variable name. -
Refusal messages now name the directory that actually refused. The ancestor walk validates the
directory it is already in, but printed the name of the child about to be entered — so #1537 was
told.cachewas at fault when the home directory was refusing, and #1621 was told
cbm-daemon-501when/private/tmpwas. Both reporters inspected exactly what we named, found it
clean, and said so. They were right, and we kept pointing at it for weeks. The message now
names the containing directory and says explicitly not to check the one it used to blame.
Errors that name what actually refused
Three fixes, one complaint: our messages did not say enough to act on.
secure CLI coordination could not be created (endpoint)now prints the validation detail —
the directory and the rule that refused. Four separate reporters hit that message; one built an
instrumented binary to discover a single ACE was the cause.- A refused agent-config write says what the target is — whether it exists, and what kind of file
it is. Nine distinct fail-closed conditions previously collapsed into one indistinguishable failure. - Neither prints
errno. It is called from 119 sites and can be stale, which is exactly how an
earlier release handed a reporter a fabricated "file not found" for a permission decision.
Config files we wrongly refused to touch
- An MCP entry your client annotated is no longer treated as foreign (#1630, #1582). OpenCode
writes"enabled": truebeside thecommandandtypewe write; our ownership check demanded an
exact key-set match, so a three-key entry with two recognised keys was classified as somebody
else's and left alone — we refused to touch an entry we had written ourselves. In one reporter's
file every MCP server carried the key, so this is OpenCode's normal shape, not an unusual hand-edit:
anyone who has ever toggled a server on or off in the UI was hit. - YAML values containing
*or&are no longer mistaken for aliases and anchors (part of #1631).
Prose asterisks, a glob inside a description, a kaomoji — all were refused as if they began a node.
The test is now positional. Root-caused by @rg6304, who reproduced it in isolation, read the
source, and corrected our hypothesis: the constructs we had guessed appear nowhere in the failing
file. This is one of four constructs in that report; the rest are still open.
Data loss and diagnosis
uninstall --helpno longer performs a real uninstall (#1038). It removed the binary and every
agent configuration for someone asking what the command does. The guard is checked before argument
parsing, so a-yelsewhere on the line cannot auto-confirm the destruction being prevented.- A crashed worker's log now survives and names its run (#1145, #1130, #1132, #1450, #1133, #1070).
Six reports ended at the same dead end: the supervisor prints the log path and the file is 0 bytes,
so the one diagnostic that would explain the failure was silenced by the failure itself. cli <tool>no longer blocks forever reading standard input (#1359) for a tool that declares no
arguments.--jsonand--progressare stripped before the stdin fallback is reached, so
cli list_projects --jsonhung too — the reporter's correction that "no flags" was not the trigger
was right.
Reliability
- Spawn retries no longer grow without bound (@moffermann). The clamp we shipped could never
fire, while the doubling it was meant to bound ran free — a future budget increase would have meant
a 40-second wait where the retry exists to avoid exactly that. Both current budgets are unchanged. - Two concurrent sessions can no longer be handed the same temporary directory
(@ertankucukoglu) — a process-global buffer in the Windows temp-directory helper. - Instrumented test builds now know they are instrumented (@moffermann). Four places asked
that question four different ways, so some sanitizer lanes measured instrumented code against
native timing budgets.
Correctness
check_index_coverageno longer reports every path as unavailable when a repository has more
than 2,000 ignored files (@SunneeYang). A truncated catalog was treated as proof that no
per-path answer existed, including for paths with a current hash record.- Go routes built by string concatenation now resolve (@AmirF194). A handler registered with
base + "/orders"produced no usable route path; the literal half is now recovered, and a path
assembled from a variable is still left unresolved rather than fabricated. - Cypher queries repeating a variable-length node no longer double it (@jstar0), matching the
unification the fixed-length path already did. - A Windows publish failure says what actually failed. The atomic publish discarded the Win32
error and reported a bareerrno, and one path returnedPERSIST_FAILEDwith no log line at all —
which is how a reporter with an ACL problem was told to check that their repository path existed. - Cross-repo intelligence works again on any project that has ever recorded a parse miss
(@vitaliy-shatskiy, #1609). Indexing writes an internal<name>::missedrow into the same
database, and the validation required exactly one row over all of them — so such a project was
rejected as "not indexed", both as source and as target, with no workaround available to the
operator. The same defect was fixed forlist_projectsin #1044; this site never learned it.
Performance
- Windows code search prefilters simple suffix globs (@ertankucukoglu), so a
*.extfilter no
longer scans and then discards. The pattern reaching the shell stays behind an allowlist, and the
original post-scan filter is retained as a second guard. - A failed search launch no longer leaks its compiled path filter (@ertankucukoglu).
search_codecan report per-phase timings on request (@ertankucukoglu) —scope_ms,
scan_msandenrich_ms, behinddebug: true. Default output is unchanged, and the measurement
itself is skipped unless asked for, so nothing is paid for by callers who do not want it.
Updating from 0.9.x is possible again
If you are on 0.9.x and update has been refusing to install, this is the release that fixes it
(#1134). v0.10.0 consolidated to one archive per platform, so the codebase-memory-mcp-ui-* names
that 0.9.x updaters ask for stopped existing and those updaters 404'd. We fixed that by publishing
byte-identical copies under the legacy names — but that step runs after checksums.txt is
generated and signed, so the aliases never appeared in it, and an updater that verifies the name it
asked for refused what it could not check:
warning: codebase-memory-mcp-ui-darwin-arm64.tar.gz not found in c...
v0.10.4
codebase-memory-mcp v0.10.4
An install-and-diagnostics release. If v0.10.3 refused to install or start for you — particularly on Windows, or on a machine where you installed cbm through a package manager — this is the one to take.
Most of what follows came from people who filed precise reports and, in several cases, root-caused the bug before we did.
Windows: elevated accounts could not install or start at all
Running as an Administrators-group account meant install failed with activation transaction I/O failed: owner-not-current-user, or the MCP server exited during startup with nothing in the log but a closed transport.
The cause was ours and specific: we compared a file's owner against the token's user SID, but Windows stamps newly-created objects with the token's owner SID — which for any member of the Administrators group defaults to BUILTIN\Administrators. So the installer created its own staging file and then refused it, seconds later, as belonging to someone else. No amount of rebooting or closing processes could help, because nothing was running.
We now also accept the SID Windows actually stamps on what we create. This is narrower than "trust any administrator": a file owned by a genuinely different account is still refused. Reported by @spam0115 and @gotspatel, and it is very likely the cause of the long-running #1416 as well.
Installing no longer touches things it doesn't own
Two changes for people whose setup we were trampling:
A binary installed by mise, Homebrew, nix, asdf or cargo is left alone. Previously install dropped a second copy into ~/.local/bin — shadowing the managed one depending on PATH order — and appended to your shell rc file. Now it configures the agents and leaves the binary and your PATH untouched, update refuses honestly and names the command that will work (mise upgrade, brew upgrade), and PATH is only modified when we actually placed the binary. Reported by @SC-CTS.
You can choose which clients get configured. install wrote MCP config, agents, skills and hooks for every detected client, and reverting the ones you didn't want was pointless because the next install recreated them. --clients=claude,codex now restricts it, and --clients on its own lists every supported token. Reported by @PsyTech-Rob.
Errors that tell you what actually happened
Three fixes in a row here, all from the same complaint: our messages were confidently wrong.
- An activation refusal no longer points at evidence it doesn't show. v0.10.3 correctly stopped blaming "active CBM sessions" for failures no session caused, but then told you to check errors that were never printed. It now names the directory and the rule that refused.
- A server that cannot start says why. Client-path startup failures wrote to stderr only, which no MCP client surfaces — so a specific, nameable refusal reached you as "transport closed unexpectedly". Those failures now arrive as a JSON-RPC error carrying the reason.
- The UI listener can be turned off without editing JSON.
ui_enabledgoverns a loopback HTTP port, and the only way to disable it was hand-editingconfig.json; it was invisible toconfig listand rejected byconfig set. Both it andui_portare now first-class config keys. Reported by @PsyTech-Rob, who spent two debugging sessions finding the switch.
Codex, OpenCode and config-file repair
- A duplicated install no longer breaks Codex permanently (@astandrik). An owned inline hook assignment is reconciled into the canonical block instead of being ignored and re-added alongside; foreign hook entries are preserved untouched, and genuinely ambiguous TOML is refused without modifying the file at all.
OpenCode installs land in the file OpenCode actually reads (@iandol).Correction (2026-08-14): this is only half true and we should not have claimed it outright. The targeting is fixed — we now write whichever ofopencode.json/opencode.jsoncalready exists, instead of always creating a.jsonfile OpenCode ignores. But the write itself still fails on v0.10.4 witherror: agent_config agent=OpenCode op=mcp_install, reported on both Linux and Windows. Tracked in #1630. This is the second release running in which we have described this fix as complete; sorry, and thank you to the reporters who kept saying it was not.- An orphan marker we wrote heals itself. An earlier install could leave a closing
# <<<marker with no opener, after which every install failed that client outright. Removal now strips the stray line. A write still refuses on an imbalance — with one marker there is no defensible region to replace.
Query correctness and platform support
OPTIONAL MATCHstopped silently dropping rows (@SEPURI-SAI-KRISHNA). Once the hop buffer saturated, the no-match rows — exactly whatWHERE x IS NULLexists to surface — were discarded. Not a crash, not a memory error: quietly fewer results, with nothing to indicate the answer was incomplete.- FreeBSD resolves its own executable path (@PR9000), which the install and ownership checks depend on, plus a build fix for platforms outside the main three.
Security
The release pipeline now pins and verifies the external tooling it downloads before executing any of it, rather than trusting whatever the upstream project publishes at build time. Verification covers every platform the job may run on, from a single pinned reference.
Reported by Andrew Hundt (#1245).
Reliability
Subprocess spawns survive a busy machine. v0.10.3 started retrying a spawn the kernel refused with "try again", but only for about 30ms — enough for a momentary dip, not for a machine that stays process-starved for hundreds of milliseconds at a stretch, which is exactly when it happens. The retry now backs off exponentially to roughly half a second before giving up. A genuinely exhausted machine still fails fast rather than hanging, and real errors — a missing binary, a permission denial — fail immediately as before.
And the release pipeline no longer publishes its registry entry before the release is public — the bug that made v0.10.3's own registry step fail with a 404 against its own assets.
Upgrade
- Binary installs: download below, or re-run
install.sh/install.ps1. - Package managers: update through them as usual — cbm will now leave that binary alone.
- npm / PyPI: as usual.
- No index, cache, or config migration.
Contributors
Code in this release came from:
- @astandrik — Codex hook reconciliation, with fail-closed handling that leaves an ambiguous config byte-identical rather than guessing
- @PR9000 (Pedro Ramos) — native FreeBSD executable-path resolution, plus a build fix for platforms outside the main three
- @SEPURI-SAI-KRISHNA — the
OPTIONAL MATCHrow-loss fix, including the analysis that separated "in bounds" from "correct" - Andrew Hundt — the release-pipeline hardening above, co-authored
And these reports found bugs we could not have found ourselves, several of them root-caused by the reporter before we looked:
@spam0115 and @gotspatel (Windows elevated accounts) · @SC-CTS (package-manager installs) · @PsyTech-Rob (four separate findings in one install cycle) · @listepo (the diagnostics that identified a refusal we had been guessing at) · @iandol (OpenCode config targeting)
Thank you — particularly to those of you who kept reporting through several releases of install trouble.
Full changelog: v0.10.3...v0.10.4
Security Verification
Before smoke and soak testing, VirusTotal completed 24 executable scans: stripped and unstripped candidates for each of the 8 release products. Every scan had at least 50 decisive engines (observed range: 59–68).
3 candidate(s) had only the documented single Microsoft machine-learning !ml result; no other decisive engine reported malicious or suspicious.
| Product | Stripped candidate | Unstripped candidate | Shipped |
|---|---|---|---|
linux-amd64 |
clean | clean | stripped (09db4f424c1fca97b5d06feb75ca6540629a5b67309e39a9a551e745b081023b) |
linux-arm64 |
Microsoft !ml |
clean | debug-stripped (e0cd04812093d420a889397a603959c9a35ea3c78dea67c44db80e25a04d29b8) |
linux-amd64-portable |
clean | clean | stripped (df1b97b0f5266924072a0c020027a14fa1890fa7457183c2106ff7eab4b62122) |
linux-arm64-portable |
Microsoft !ml |
clean | debug-stripped (8427e22d1445b1e4a9a883034c12c18eb78c30e6ba183235ca79e153641a39e9) |
darwin-amd64 |
clean | clean | stripped (9ae79b267fe60438ffbba046bb5ed9e36b2b0f0c04e32970ee6995d616a8ccc6) |
darwin-arm64 ... |
v0.10.3
codebase-memory-mcp v0.10.3
A fixes-only release, and most of it arrived as finished work from the community: six contributed pull requests are in here, alongside repairs for two regressions we shipped ourselves in v0.10.2.
Two things worth knowing before you upgrade. If you run more than one MCP client against the same project, this release closes a bug that could silently destroy a healthy index — start with the first section. And if installing or updating has been failing for you, three separate causes are fixed here: a permission rule that refused ordinary home directories, an error message that reported the wrong reason entirely, and Codex losing your cache directory. Several of those failed silently or blamed the wrong thing, which is why they survived multiple releases.
Concurrent instances no longer quarantine each other's databases
The worst bug in this release, fixed by @LynxBay. When two cbm instances raced on the same project — two editors, an editor plus a CLI call, a daemon and a stray session — the loser of the lock race could conclude the database was corrupt and quarantine it: renamed away, rebuilt from scratch, hours of indexing gone. The database was healthy; the integrity check just couldn't tell "damaged" from "busy" (#1206).
The same check had the opposite blind spot too: a database with genuinely torn pages passed as long as its top-level bookkeeping looked right, so real damage went unnoticed until queries misbehaved (#1037).
Integrity now returns a three-way verdict. Only confirmed corruption quarantines; a lock race says "project is busy; retry after indexing" and leaves the data alone; and a deep quick_check catches page-level damage the shallow probe missed. Both directions carry regression tests.
Graph determinism, twice
Two contributions from @Studnicky close two long-standing sources of run-to-run drift — the "same repo, slightly different edge counts" behavior tracked in our known issues since the 0.9.1 RC:
- When the same logical edge is produced twice with different attributes (an LSP-resolved call and a textual match, say), the survivor was whichever arrived last — a function of thread scheduling. The merge is now a total order: higher confidence wins, ties break deterministically, and the outcome is independent of arrival order by construction.
- CONFIGURES candidate collection sorted nothing before applying its cap, so which candidates survived depended on worker merge order. Candidates are now ordered by a pure content key first — notably not by node id, which looks stable but is itself handed out in merge order.
Windows: daemon files created with an empty ACL
@Kiborgik diagnosed and fixed a subtle one (#1351): the daemon's private directory combined a protected DACL with a non-inheritable ACE, which on Windows means children inherit nothing — every file created inside was born with an empty ACL and unreadable even by its owner. Directories now carry a properly inheritable owner-only ACE, so the strict security posture is kept and the files actually work. A companion fix from the same contributor closed a trap where a machine named like its user granted permissions to an empty principal; testing that on a non-domain-joined machine surfaced the mirror-image trap, so our build tooling now identifies the account by SID and stops resolving names altogether.
Repairs for two v0.10.2 regressions
Both reported within hours — thank you for the fast signal.
update --uistopped working (#1544, @ehendrix23). v0.10.2 removed the obsolete ui/standard chooser and the flags that drove it, so a command living in scripts and muscle memory became a hard error. Both flags are accepted again — they do nothing, say so once, and genuinely unknown flags still fail.- The generated Pi extension broke Pi entirely (#1550, @musichen). Pi loads an extension by calling its default export; ours exported a named function instead, and a Pi extension that fails to load takes every
picommand down with it,pi doctorincluded. The extension now default-exports its factory. Fixed independently by @musichen and @henri-edh as well, whose sharper description of Pi's loader is the comment now in our source.
Structured output stops dropping evidence
trace_path(include_evidence: true) promised per-hop strategy and confidence columns, and delivered them — in tree format only. With format: "json" both fields silently vanished, which hit exactly the callers most likely to want them: the ones consuming structured output programmatically (#1542, @asemraza). The JSON path now carries both fields with the same semantics, nulls standing where the tree format prints -.
Subprocess spawns survive a busy machine
Chasing a CI flake uncovered a real robustness gap: when the kernel refuses a process spawn with "try again" — a momentarily full process table on a loaded machine — cbm treated it as a permanent failure. A git probe or LSP server would refuse to start for a reason invisible to the user. Transient spawn failures now retry briefly; real errors (missing binaries, permissions) still fail immediately with unchanged semantics.
Install and activation: the refusal nobody could act on
Follow-up to #1537 (@listepo), and it turned out to be two defects stacked on each other.
The daemon refused to run when any ancestor of its cache directory was group-writable — the same rule we removed on the installer side in v0.10.2, in the sibling check that decision covers but that never got changed. A group-writable ~ or ~/.cache is ordinary (WSL2 ships 0775, so do several distro skeletons and any machine with a shared primary group), so for those users the daemon was permanently unusable, with nothing to close and nothing a reboot would fix. World-writable ancestors are still refused — any local user could swap a path component — and the private directory itself is still forced to owner-only, which is what makes admitting the ancestor safe.
Underneath that, the error message was inventing its own evidence: the refusal path set no error code, so the reporting fell back to printing whatever errno happened to hold, and handed the reporter ENOENT for a permission decision. They went looking for a missing file that was there the whole time. The message now names the directory and the rule that refused it.
Also in this area: the activation guard reported every refusal as "active CBM sessions could not be stopped", including failures where nothing is running at all. Those are now separate messages, the second says plainly that nothing needs closing, and neither remedy assumes a codebase-memory-mcp binary is still on PATH — advice that was unusable at exactly the moment it printed, right after uninstall.
Also fixed
- Codex saw no cbm tools when
CBM_CACHE_DIRwas set (#1562, @anton-matosov). Codex passes only explicitly-listed variables into an MCP subprocess, so the server it spawned read the default cache while the daemon used the configured one; they disagreed and the connection closed during initialization. The generated config now forwards the variable. - The installed skill file failed to load in strict YAML readers (#1554, @xezzon). Its
descriptioncontainedTriggers on:unquoted, and a colon-space in an unquoted YAML scalar means "nested mapping" — so parsers rejected the whole document and the skill silently never loaded. search_codeechoed a negativelimitback as the result count (#1511, fix by @lukiod), which reads to an agent as an answer rather than a rejected argument.OpenCode installs went into the wrong file (@iandol).Correction: this was listed here in error. The fix merged a few hours after the v0.10.3 tag was cut, so it is not in this release — it ships in v0.10.4. If your OpenCode config is a.jsoncfile, upgrading to v0.10.3 will not have helped, and we are sorry for the wasted time.- Release archives are validated before extraction (#1510, @cyphercodes): an exact member set, with symlinked members refused outright.
Upgrade
- Binary installs: download below, or re-run
install.sh/install.ps1— re-running the installer is the update. - npm / PyPI: update through your package manager as usual.
- No index, cache, or config migration.
Full changelog: v0.10.2...v0.10.3
Security Verification
VirusTotal completed 17 distinct extracted byte objects covering 56 exact extracted archive members.
The extraction manifest binds those associations to 14 downloadable archives by SHA-256 provenance. Downloadable .tar.gz/.zip release containers were not submitted to VirusTotal.
16 of 17 scanned objects returned 0 malicious and 0 suspicious verdicts with 57–68 decisive engine results (required minimum: 50).
1 object(s) carry a single Microsoft machine-learning detection (!ml), which this project treats as a known false positive and publishes rather than hides. Every other engine returned clean. See Antivirus False Positives for the evidence and for how to verify these artifacts yourself: objects/scan-c05a4598b3bb23d82efbde5e6bc01989297156ed153dadbd281ba8886c96f2d6--codebase-memory-mcp
Durable public evidence: associations, exact scan set, per-extracted-object results and report links, evidence checksums.
Archiv...
v0.10.2
codebase-memory-mcp v0.10.2
An install-and-upgrade release. If v0.10.0 or v0.10.1 refused to install, refused to update, or your MCP client showed a server that connected to nothing — this is the fix, and every one of those failures came from a user report filed in the last 24 hours.
The pattern behind all of them: gates that were right in principle refused ordinary, legitimate setups, and then didn't say why. Each one below keeps the protection that matters, drops the part that was refusing real users, and now names exactly what it refused and how to proceed.
npx (and every ephemeral install path) works again
Running cbm through npx codebase-memory-mcp produced a server that answered nothing: the client waited 30 seconds and exited with zero bytes on stdout, which agents surfaced as -32000: Connection closed. Every npx invocation hit this, deterministically.
The daemon verifies a connecting client's binary image, and it was treating two very different outcomes as one failure: "this image hashes differently than mine" (the tamper case the check exists for) and "this image cannot be examined at all" — which is what an ephemeral npx cache path always produces. The second is now admitted: the handshake immediately before it has already proven semantic version, build fingerprint, and protocol/store/feature ABI, so refusing on top of that traded a real compatibility proof for an unavailable one. A genuine fingerprint mismatch still rejects, hard, and the admission is logged out loud so the weaker check is never invisible.
Separately and unconditionally: a client that cannot reach the daemon now says so in JSON-RPC, naming the reason, instead of exiting silently. Thanks to @wassolles, who arrived with the reproduction, the admission path already read, and three candidate fixes — the one we shipped was theirs.
Installing into an ordinary Linux home works again
install.sh failed with error: failed to stage install candidate: activation transaction I/O failed — a message that sent people hunting disk errors and filesystem types for what was, in fact, a permission policy: the installer required no group-write bit on the install directory and every parent above it. WSL2 ships ~ and ~/.local at 0775, as do several distro skeletons and any site using a shared primary group.
World-writable parents are still refused — any local user could swap a path component mid-install. Group-writable parents are now warned about and accepted, while the install directory itself stays strictly owner-private: that is where the binary lands, and group write there would let another account replace the executable between verification and launch. Refusals now name the directory, its mode, and which rule refused.
Root-caused by @AmirF194 in a clean container, down to the exact predicate and line; independently confirmed by @shochdoerfer and @iandol.
Updating from 0.9.x works again
v0.10.0 consolidated to one archive per platform with the graph UI always embedded, but update still asked which variant you wanted — where "ui" could only 404, and "standard" quietly was the UI build. The chooser and its flags are gone.
Binaries already installed in the field can't be fixed retroactively, so releases now also publish ui-*-named copies of each archive. Every 0.9.x updater that asks for the old name gets the same bytes and completes normally, with nothing required from you. Reported by @iandol.
macOS install diagnostics
install.sh no longer prints No such xattr: com.apple.quarantine — a harmless line (a curl-downloaded archive simply carries no quarantine attribute) that was mistaken for the cause of an unrelated failure, to the point of becoming a bug report's title. And when installation is genuinely blocked by a running session, the error now points at codebase-memory-mcp daemon status to list the processes actually holding it, instead of asserting that sessions exist and leaving you to find them. Reported by @listepo.
Upgrade
- Binary installs: download below, or re-run
install.sh/install.ps1— re-running the installer is the update. - npm / PyPI: update through your package manager as usual.
- From 0.9.x: the old updater works again as of this release; if it already failed for you, re-running the installer is the fastest path.
- No index, cache, or config migration.
Full changelog: v0.10.1...v0.10.2
Security Verification
VirusTotal completed 14 distinct extracted byte objects covering 32 exact extracted archive members.
The extraction manifest binds those associations to 8 downloadable archives by SHA-256 provenance. Downloadable .tar.gz/.zip release containers were not submitted to VirusTotal.
13 of 14 scanned objects returned 0 malicious and 0 suspicious verdicts with 55–66 decisive engine results (required minimum: 50).
1 object(s) carry a single Microsoft machine-learning detection (!ml), which this project treats as a known false positive and publishes rather than hides. Every other engine returned clean. See Antivirus False Positives for the evidence and for how to verify these artifacts yourself: objects/scan-cac31c5358f3a1dcccc4d4ccbe2591f6fbae58949d23bb341429a6197e93a64b--codebase-memory-mcp
Durable public evidence: associations, exact scan set, per-extracted-object results and report links, evidence checksums.
Archive SHA-256 provenance (from the extraction manifest):
| Downloadable archive | SHA-256 provenance |
|---|---|
codebase-memory-mcp-darwin-amd64.tar.gz |
bb6cb47aea9e50e2193cdd917d5dbafa63b7d9c1cbe74bab5ec9bf4faa67e295 |
codebase-memory-mcp-darwin-arm64.tar.gz |
fa3ee085485fd9c16d1c1bd8a102df518862dabd2d6a09e4c6d8dfb3cd2a7eb4 |
codebase-memory-mcp-linux-amd64-portable.tar.gz |
4ead161715fe959ef8a0e38946d63b1728d31e5fb1762a85dbb4b8c4abfbe481 |
codebase-memory-mcp-linux-amd64.tar.gz |
6e3bb7353be21407a78e67b5465e53e3afb1a4a213e7a561606900ac08dcfdd6 |
codebase-memory-mcp-linux-arm64-portable.tar.gz |
dcd6f31f4004c25e7945b355ae23a5a381acb2a620301bca93e1ddb159b672fd |
codebase-memory-mcp-linux-arm64.tar.gz |
b70148686cec55c31673fc0cebc0caf7f664f4ae29f7ba7f07b9617c2e5eaf85 |
codebase-memory-mcp-windows-amd64.zip |
8f08e5c5b480e625adf9d4560765a860d493a690df6ded5b94127283ec5b660a |
codebase-memory-mcp-windows-arm64.zip |
b77b81975021b879ca53cdd94c60b48cc5de102194b52117938a2187a42f89c6 |