Added
- Compressed output is now visibly reversible — a first-class recovery layer on
every surface (#625). Agents (Codex especially) were re-reading compressed
views line-by-line because nothing taught them how to get the raw bytes back —
the real cause behind "too compressed" reports. The escape hatch already
existed; it is now discoverable and consistent, with the non-MCP path
treated as first-class (many orgs forbid MCP):- Proactive (MCP rules v4). A new
RECOVERrule states the invariant —
compressed output is reversible, never re-read it line-by-line — and names the
recovery grammar: read the shown file path with any tool (no MCP), or
ctx_read(mode=full|raw=true);[Archived]/tee/firewall handles →
ctx_expand(id=…). - Reactive (ctx_read footer). Every lossy
ctx_readview ends with a
recovery footer that leads with the native file path, then the MCP fallbacks;
full/rawviews carry no footer (deduped by mode).ctx_readgained an
explicitrawparameter (verbatim bytes, unframed escape hatch). - CLI / shell-hook surface.
lean-ctx raw "<command>"is now surfaced in
help,help alland the cheatsheet, so the raw path is reachable without
MCP. - Guaranteed raw on disk. The default
tee_modeis nowhigh-compression:
whenever a view compresses heavily a verbatim copy is onectx_expand/file
read away. The newrecovery_hintsconfig (off|minimal|full) tunes footer
verbosity. All recovery grammar (archive, firewall, spill, tee, ctx_shell)
flows through onerecoverymodule, so every channel speaks the same
non-MCP-first sentence.
- Proactive (MCP rules v4). A new
Fixed
- Source reads are no longer silently stripped of decorative separator comments,
which had broken follow-up edits (#628). A file read carries lossless-only
intent — the model edits exactly what it sees — so the proxy never lossy-
compresses a recognized file read. The safety net for reads routed through an
unrecognized tool (the content heuristic) under-counted real source, though: a
decorative separator comment (// ————,// ----) and the call-shaped
scaffolding of a test file (describe(…) {,});) scored as non-code, so a
genuine.test.tscould be compressed on the wire and lose those separator
lines — after whichctx_editfailed on a whitespace mismatch against the
on-disk file. The heuristic now treats comment lines as neutral (they never
dilute the code ratio) and recognizes top-level call/closer shapes as code, so
real source is protected regardless of the originating tool. Regression tests
pin the verbatimctx_readmodes (full/raw/lines:N-M) to reproduce every
source line, including decorative comments. - The Codex
lean-ctx -cSessionStart hook is no longer a redundant nag (#625).
Codex'sPreToolUsehook already rewrites every rewritable Bash command to
lean-ctx -c "<command>"transparently (permissionDecision: allow+
updatedInput), so the old SessionStart line ("preferlean-ctx -c") taught
nothing actionable — and crucially said nothing about getting raw output,
which is the one thing an agent cannot reach once a command is auto-compressed.
The hint now teaches the raw escape (lean-ctx raw "<command>") and forbids the
small-chunk re-read anti-pattern. The raw spellings (lean-ctx raw,
lean-ctx -c --raw) are reentrance-safe: the rewrite hook leaves any command
starting withlean-ctxuntouched, so the agent always reaches verbatim bytes. ctx_shellnow surfaces when a requestedcwdwas rejected by the project-root
jail (#629). Acwdresolving outside the session'sproject_rootis rejected
by the path jail and silently replaced with the project root — correct sandboxing
(it stops MCP clients escaping the workspace), but the silent fallback made the
parameter look ignored: a caller runningpwd && lsin what they thought was dir
A actually ran in the root with no indication why. The jail is untouched;
effective_cwd_checkednow returns the rejection reason andctx_shellappends a
one-line[cwd: …]hint naming the reason and the directory it ran in instead.
Thanks to @mahmoudps for the report and the original patch (#630).ctx_searchno longer returns a falseNo matchesfor content edited after
the index warmed (#624). The resident trigram index treated itself as fresh
for a fixed 15 s TTL with no per-file change detection, so in hybrid setups
where edits are applied natively a freshly edited or created file was invisible
to trigram narrowing — for a word-literal query a missing trigram made the
candidate set provably empty, yielding0 matcheseven though the text was on
disk (andget_fresheven served a stale index past the TTL, so results
flipped between hits and misses depending on timing). Index freshness is now a
function of corpus state, not the clock: the build records a cheap,
order-independent signature over the eligible files'(path, mtime, size), and
every lookup re-derives it via a stat-only walk that shares the build's exact
filter path — the resident index is served only when the signature matches the
live filesystem, otherwise lean-ctx walks accurately for that call and rebuilds
the index in the background. The optionalLEAN_CTX_SEARCH_INDEX_COALESCE_MS
(default0= always verify) coalesces the stat-walk under bursty load on very
large indexed trees.ctx_readwas never affected (it is mtime + MD5 verified).- No-auth dashboard no longer 403s behind a port-remapped Docker publish (#623). In
no-auth mode every/api/*request is gated on aHostallowlist built from the
bound port. When a container binds0.0.0.0:3333but Docker publishes it on a
different host port (-p 60000:3333), the browser reaches127.0.0.1:60000and
sendsHost: 127.0.0.1:60000— the published port, which the bind-port
allowlist (127.0.0.1:3333) rejected, so every API call returned 403 and the
dashboard's cards failed to load. The gate now accepts any loopback host
(127.0.0.0/8,localhost,::1) on any port: a loopbackHostcan't be a
DNS-rebinding target, so this is safe, and cross-origin/CSRF is still blocked by
theSec-Fetch-Site/Originchecks. Port-remapped loopback publishes now work
out of the box withoutLEAN_CTX_DASHBOARD_ALLOWED_HOSTS.
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.18...v3.8.18