Added
- Dashboard: sort the live call feed by per-call cost (#426) — the Live
Activity feed already showed per-call detail (tool, file/query, tokens in →
out, tokens saved, read mode); it now has a Sort selector — Recent / Top
saved / Largest / Slowest — so you can rank tool calls by cost and instantly
see which reads/searches/shell calls were expensive vs cheap. Read-only,
reuses the existing/api/eventsjournal data; no new routes. - Dashboard: Quick Settings — flip core switches from the UI (#427) — a new
Settings tab (Context area) flips the four high-impact, mid-session
switches without dropping to the terminal: compression level
(off/lite/standard/max), tool profile (minimal/standard/power/lean),
structure_first(on/off) and terse agent (off/lite/full/ultra). Writes go
through a new/api/settingsendpoint that inherits the dashboard's
Bearer-token auth and CSRF-Origincheck, validates every value against the
config schema and a fixed four-key allow-list (no arbitrary config keys
are writable), and persists toconfig.tomlexactly like the matching CLI
commands. Settings pinned by aLEAN_CTX_*environment variable are flagged
in the UI so a toggle never silently no-ops. - Dashboard:
--open=browser|none|vscodereveal control (#424) —lean-ctx dashboardalways launched the system browser, which is jarring inside an
editor or behind a reverse proxy. A new--open=<mode>flag (or--no-open),
resolved as--open>LEAN_CTX_DASHBOARD_OPEN> the browser default, picks
the reveal behaviour:browser(launch the system browser, unchanged default),
none(start silently and just print the URL) orvscode(suppress the
external browser and print the VS Code Simple Browser steps). Flag parsing is
case-insensitive and falls back tobrowseron an unknown value.
Fixed
-
macOS: the "lean-ctx wants to access your Documents folder" prompt no longer
returns after every update (#356) — lean-ctx binaries are ad-hoc signed, so
their cdhash changes on every build. macOS TCC anchors an ad-hoc binary's
privacy grant to that cdhash, so each update looked like a brand-new program and
re-popped the prompt — clicking "Allow" only lasted until the next build. New
lean-ctx codesign-setup(macOS) creates a dedicated keychain with a persistent
self-signed code-signing identity and trusts it once (a single Touch ID / login
password confirmation).dev-installand the self-updater now sign every build
with that identity, giving TCC a stable Designated Requirement
(identifier "com.leanctx.cli" and certificate leaf = H"…") instead of a
per-build cdhash. Result: a single "Allow" survives all future updates. Falls
back to ad-hoc signing when the identity isn't set up, so the binary always runs. -
doctor --fixnow fully empties~/.lean-ctxinstead of leaving items behind
(#429) — the XDG split migration skipped any entry whose destination already
existed and left the source in place. On Windows (and after any partial
earlier run or a parallel data dir) the targets routinely pre-existed, so ~30
legacy items lingered anddoctorwarned about the single-dir install forever,
no matter how often you ran--fix. Collisions are now reconciled instead of
skipped: directories are merged child-by-child, a source file byte-identical
to the destination is dropped as a duplicate, and a genuinely different source
is moved aside next to the winner under a*.legacyname. The destination is
never overwritten and nothing is lost, so the legacy directory empties out and
the warning clears.doctor --fixnow reportsN moved/merged, N duplicate(s) dropped, N kept as *.legacy. -
macOS TCC "Documents" prompt — definitive structural fix (#356) — the
privacy prompt asking to access your Documents folder, which kept returning
after everylean-ctx updatedespite earlier patches (v3.8.0, v3.8.2), is now
fixed at the root. The TCC guard (may_probe_path) was opt-in per call site,
so every new or forgotten heuristic filesystem probe re-introduced the prompt
(whack-a-mole). The model is inverted to a choke-point / opt-out design:safe_canonicalize— the sink that ~8 heuristic call sites funnel through —
returns the path lexically (nostat/realpath) when the process is
launchd-standalone and the path is under~/Documents,~/Desktopor
~/Downloads.- every duplicated project-marker probe (
config,graph_index,setup,
dashboard,knowledge_bootstrap,graph_provider) now routes through the
single guardedpathutil::has_project_marker, with one marker set. is_safe_scan_rootrefuses launchd-standalone scans under the protected dirs
before any marker probe orread_dir;has_multi_repo_childrennow also
refuses nested protected paths (e.g.~/Documents/proj), not just the bare
magic dirs. The project-local.lean-ctx.tomlread and thegit rev-parse/
cwd-fallback in project-root detection are guarded too.
Why it kept coming back:
lean-ctx updaterun from a terminal makes the daemon
inherit the terminal's TCC grant, masking the bug; end users run the daemon and
proxy as LaunchAgents (ppid 1, standalone), where the unguarded probes hit
~/Documentsand prompt — and every update changes the binary's code signature,
invalidating any prior grant. A new macOSsandbox-execregression test
(rust/tests/tcc_sandbox.sh) boots the daemon as a standalone process under a
profile that SIGKILLs on any~/Documentsaccess, reproducing the real
end-user condition that terminal testing hid, alongside standalone unit tests
inpathutil/graph_index/session.Note: installing the update that contains this fix may show the prompt one
last time (the old, still-running binary's signature changes as it is
replaced); after that it stays quiet. -
auto_update_mcp = falsenow suppresses MCP writes on every registration
path (#281) — earlier fixes only gated the shared JSON-config writer and
configure_agent_mcp; the per-agent hook writers (Claude, JetBrains, OpenClaw,
Crush, OpenCode) and the editor-registry registration in interactive setup,
non-interactive setup anddoctor --fixstill wrote MCP server entries
unconditionally. The check is now centralized inhooks::should_register_mcp()
and applied on every path: hooks, rules and skills still install, only the MCP
server entry is withheld. A subprocess regression test guards it. -
ctx_readmap/signatures no longer serve pre-rebuild output after
ctx_index build-full(#420) — the CLIbuild-fullpath cleared the daemon
read cache, but the MCP tool runs in the process that owns theSessionCache,
so a forced rebuild leftctx_read map/signaturesreturning stale output.
The MCP tool now invalidates the in-process graph cache and clears the
SessionCachein-process, matching the CLI guarantee. -
Dashboard auto-refreshes the active view on data change and tab focus
(#425) — the 10s poll only refreshed the status bar and flagged the manual
refresh button; the main panels listen tolctx:refresh, which only the manual
button dispatched, so stats/metrics stayed static until a reload. The poll now
dispatcheslctx:refreshon a content-hash change while the tab is visible
(panels reload in place, preserving UI state), and avisibilitychangehandler
catches up immediately when the tab regains focus. -
lean-ctx watchbackfills recent events on start (#560) —watchset the
tail offset to EOF on startup, so an idle launch showed a blank screen even
whenevents.jsonlwas already populated. It now seeds the view with the last
20 events (bounded, O(n) memory) and advances the offset to EOF, so the live
poll stream continues without re-emitting them. -
Homebrew installs no longer run a stale shadowed binary (#559) — a
brew-managed shim (/opt/homebrew/bin/lean-ctx→../Cellar/lean-ctx/<old>)
could shadow the freshly built~/.local/bin/lean-ctxonPATH, so the daemon
and CLI ran different builds (md5 drift). After installing, lean-ctx repoints
any Cellar/linuxbrew shim at the just-installed binary and warns about any other
PATHentry that still resolves before it. (The drift helper is correctly
gated to unix so the Windows cross-compile stays warning-clean.) -
JetBrains plugin ships under a discoverable release-asset name (#418) —
buildPluginemittedlean-ctx-<version>.zip, indistinguishable from a source
archive in the GitHub Release asset list, so the plugin looked "missing" even
though it was attached. The artifact is renamed to
lean-ctx-jetbrains-plugin-<version>.zipbefore upload, and the release job
now fails loudly ifbuildPluginproduced no zip.
Security
- PathJail keeps resolving symlinks under TCC-protected dirs (#356 follow-up)
— the #356 choke-point accidentally routed PathJail's canonicalization through
the same TCC guard (canonicalize_or_self→safe_canonicalize_bounded→
safe_canonicalize), so a launchd-standalone daemon validating a path under
~/Documentsgot a lexical (unresolved) path and could miss a symlink jail
escape. Security canonicalization is now split from heuristic canonicalization:
PathJail (jail root, candidate ancestor, extra-roots, TOCTOU re-check, and the
allow-list) uses a new unguardedpathutil::canonicalize_secure[_bounded]that
always resolves symlinks; only self-initiated heuristic probes keep the guard.
The jail only ever runs on a path the client explicitly asked for, so a
one-time prompt there is legitimate, while #356's self-initiated boot prompts
stay suppressed (verified by thesandbox-execboot test plus a new
canonicalize_secure_bypasses_tcc_guard_for_pathjailunit test). - Cookbook dev-dependency upgrade — Vite 6 → 8 (#595) — the example apps now
build on Vite^8.0.16with@vitejs/plugin-react^6(peervite ^8),
pulling a patched esbuild and clearing the esbuild dev-server advisory
(GHSA-67mh-4wv8-2f99).npm auditreports 0 vulnerabilities; the
knowledge-graph-explorer example builds and typechecks unchanged. Node engine
floor raised to>=20.19.0to match Vite 8's requirement.
Upgrade
lean-ctx update # recommended (auto-downloads + refreshes shell hooks)
cargo install lean-ctx # or
npm update -g lean-ctx-bin # or
brew upgrade lean-ctxNote: After upgrading via cargo/npm/brew, run
lean-ctx setupto refresh shell aliases.lean-ctx updatedoes this automatically.
Full Changelog: v3.8.7...v3.8.7