Update dependency github:yvgude/lean-ctx to v3.10.5 - #2313
Open
renovate[bot] wants to merge 2 commits into
Open
renovate[bot] wants to merge 2 commits into
renovate[bot] wants to merge 2 commits into
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
renovate
Bot
force-pushed
the
renovate/github-yvgude-lean-ctx-3.x
branch
2 times, most recently
from
September 25, 2026 23:26
b2cf3b2 to
9bfe341
Compare
renovate
Bot
force-pushed
the
renovate/github-yvgude-lean-ctx-3.x
branch
from
September 26, 2026 13:44
7baadbd to
35c0c6d
Compare
renovate
Bot
force-pushed
the
renovate/github-yvgude-lean-ctx-3.x
branch
from
September 27, 2026 21:30
9ae7008 to
5eb3b5d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
v3.8.8→v3.10.5Release Notes
yvgude/lean-ctx (github:yvgude/lean-ctx)
v3.10.5Compare Source
Fixed — MCP configs survive package-manager updates
the next update removes.
setup,init,doctor --fixandwrapwrotethat directory into every agent's MCP
commandand hooks, so after anupdate the agent pointed at a deleted binary (#1873). They now write the
stable launcher on
PATH(the Scoop shim, the Homebrew symlink), as theshell hooks already did since #1851.
lean-ctx doctorreports an oldversioned path as drift, and
lean-ctx doctor --fixrewrites it.Fixed —
ctx_search query=searches the session's projectproject_rootpinned inconfig.toml(orLEAN_CTX_PROJECT_ROOT),the BM25 form of
ctx_search(query=,action=semantic, and the"Ranked files" section of
ctx_compose) searched the pinned project'scorpus from any session, while
action=regexandaction=symbolsearchedthe session's own project (#1875). A pinned root now applies only to paths
inside it. The result header names the root it searched
(
… from 1994 indexed chunks in /path/to/project).Fixed — concurrent identical
ctx_shellcalls all get their answerctx_shellcalls issued at once share onejob. The first to finish removed it, so the others waited until their soft
cap and returned "detached" with a job id that no longer existed (#1876).
The job is now removed only after every waiting call has read it, and never
while a background caller can still poll it by id. A call that arrives
after the shared job finished but before its result was read joins that
result instead of replacing the entry, which lost the answer for fast
commands such as
true.Fixed — warn-only shell findings no longer read as a block
shell_security = "warn", a command outside the allowlist runs, butthe log line was the enforce-mode block message ("BLOCKED — DO NOT RETRY
… permanent restriction") at WARN level (#1874). It is now logged at INFO
as
warn-only: … it ran (<reason>).Fixed —
lean-ctx serve --helpprints real line breaks\n\sequences on one line. It is now oneoption per line.
Upgrade
Full Changelog: yvgude/lean-ctx@v3.10.4...v3.10.5
v3.10.4Compare Source
Fixed —
lean-ctx update --helpno longer installs an updatelean-ctx update --help(and-h, and the same flags onenable-gpu)now print the command's options. Before, the flag was ignored and the
command downloaded and installed the latest release.
--chek, is refused with exitcode 2 instead of running an update.
--forceand--rewireare stillaccepted, and still have no effect, because older instructions mention them.
Fixed — the value status line now reaches existing Claude Code installs
◆ lean-ctx −13.1K tok) to ClaudeCode, but it was only written by
lean-ctx init --agent claude. Installsthat were updated, or set up with
lean-ctx setup, never got it. The hookrefresh that runs on update and when the MCP server starts now sets it too.
A status line of your own is never replaced. To switch the status line off,
set
[value_display] mode = "off"."statusLine": nullentry in Claude'ssettings.jsonis now replaced.Before, lean-ctx reported the status line as set but wrote nothing.
saved at least 10K tokens (was every 10 turns and 50K, which most sessions
never reached). A security event still always gets a recap. Existing
recap_every_turns/recap_min_tokenssettings are kept.Fixed — OpenCode, Windows paths and allowlist noise
read,grep,globandbashto"ask"instead of
"deny"inopencode.json(#1864). OpenCode removes a deniedtool from the request, and the OpenCode Zen free tier refuses requests
without
bashandreadwith a 403.lean-ctx setuprewrites the"deny"entries older releases wrote; uninstall removes both values.lean-ctx read/grep/lscommands now quote the lean-ctxpath. Before, a binary under a path with a space, such as
C:\Program Files\..., was split by the shell and the command failed withexit 127 (#1865).
interpreter) are logged at
infoinstead ofwarn, so they no longer showup on stderr of every command. Log output carries ANSI colour codes only
when stderr is a terminal (#1866).
git --version | head -1; python --versionis no longer reported as a pipeinto a bare interpreter. Only the command that actually receives the pipe is
checked (#1867).
Fixed —
ctx_shellcan no longer suspend the agent in your terminalctx_shellnow starts in its own session without acontrolling terminal. Before, an interactive shell somewhere below it (a
test suite that runs
zsh -iorbash -i, for example) took over theterminal, and the terminal stopped Claude Code: zsh printed
suspended (tty input) claudeand the terminal was left in mouse mode.Commands that need a terminal now fail right away instead of freezing
the agent.
when
cargo testruns in one.Upgrade
Full Changelog: yvgude/lean-ctx@v3.10.3...v3.10.4
v3.10.3Compare Source
Added —
lean-ctx value: what lean-ctx did, with prooflean-ctx valueshows the tokens kept out of the model's context and thesecurity events of the current session.
--session <id>picks anothersession,
--allcovers every session, and--jsonprints the result asJSON. The numbers do not come from the display counters. They are
recomputed from the hash-chained savings ledger and the signed audit trail.
Both chains are checked from their first entry, and the output names the
first and last entry hash each number rests on. When a chain is broken, the
numbers are marked as not proven and the command exits 1.
Ledger events now include the session id in their hash (canonical v7), so
a session's savings can be proven on their own. Entries written as v1–v6
still verify.
Protections that fire by default now leave a signed audit-trail entry:
reaches the model)
The entry records the kind, the count and the session.
New
[value_display]config section (mode = off | minimal | milestones | verbose, defaultminimal, envLEAN_CTX_VALUE_DISPLAY). It controls thevalue surfaces added in later changes. The proof chains are written
regardless of the mode.
Added — Claude Code shows what lean-ctx did, outside the model's context
lean-ctx statuslineprints one dim line for Claude Code'sstatusLine, for example◆ lean-ctx −1.2M tok · 41 cached · ⛨ 3(⛨ countssecurity events). It reads the snapshot of the project Claude Code is
working in. It prints nothing when nothing was measured yet, when the
numbers are older than 12 hours, or when they belong to an earlier
conversation.
init --agent claudesets it only when there is no statusline yet, or when the existing one is already lean-ctx's. If you have your
own status line, it stays untouched and
initprints the command thatchains it:
lean-ctx statusline --wrap '<your command>'. The wrappedcommand gets the same input, and lean-ctx's segment is appended to its
first line. Uninstall gives your wrapped command back and removes only
lean-ctx's own entry.
recap_every_turns), the Stop hook shows aone-line
systemMessagesuch as◆ lean-ctx · last 10 turns: −312.0K tokens, but only when at least 50,000 tokens were saved(
recap_min_tokens) or a security event happened. A quiet window keepsgrowing until it is worth a line.
the last session once, with its share of tool input
(
◆ lean-ctx · last session (62% of tool input): −1.4M tokens). Once aweek it shows a digest of all sessions instead. Nothing is shown on
resume, compact or clear.
systemMessageand status-line output. They never reachthe model's context. The SessionStart rules and the recap are written as a
single JSON object, because a host reads only one per hook. Hosts that do
not show
systemMessage(Codex, Cursor) get no recap.mode = offturnsall of it off, and
lean-ctx valueproves every number.Added — what lean-ctx did, in your prompt, your desktop and your commits
lean-ctx init --promptadds a dim segment such as◆ −1.2M tok ⛨ 3for the project you are in. It goes on the right in zshand fish, and in front of
PS1in bash. The segment has its own rc block,your prompt stays as it is, and
init --prompt offorlean-ctx uninstallremoves it. Under Starship,
initprints acustommodule instead ofediting rc files.
lean-ctx prompt-segment --shell plainserves any otherprompt engine. It reads one small snapshot file and prints nothing when
there is nothing current to show.
mode = milestones: a desktop notificationthe first time you reach 1M/10M/100M/1B tokens kept out of context, the
first secret kept out of context, the first risky command blocked, or a
7/30/100-day streak.
verification triggers none.
Proof: lean-ctx value --all.osascript,notify-sendor a Windows toast, with no newdependency. The text is passed as arguments, never as script.
lean-ctx init --git-trailerinstalls aprepare-commit-msghook that addslean-ctx: 840.0K tokens saved, 1 secret kept out of contextto a commit message.commit.
(printing the line to add instead).
gain --wrappedand its compact form list theperiod's security events, re-counted from the audit trail. They appear only
when the trail verifies.
every surface, including Starship and Powerlevel10k.
Added —
lean-ctx prove speed: a signed measurement instead of a speed claimlean-ctx prove speed --suite <file>asks your model each task of an evalsuite twice per run: once with a raw context dump, once with lean-ctx's
context, both within the same token budget (
--budget, default 4000). Itneeds a live, OpenAI-compatible endpoint (
LEAN_CTX_EVAL_MODEL_URL,LEAN_CTX_EVAL_MODEL, optionalLEAN_CTX_EVAL_MODEL_KEY), local Ollamaincluded. Recorded responses are refused, since they have no latency.
arm that goes first alternates per task and round, and each arm's latency is
the median of
--runsrounds (default 3). Every answer is scored, so afaster but worse result is reported as worse.
<data_dir>/value/speed/,--outfor acopy,
--jsonto print it).prove speed --verify [FILE]re-checks thesignature and recomputes the summary from the raw timings. A modified proof
prints
TAMPEREDand exits 1.gain --wrappedquotes your latest proof (<N>% faster model answers with lean-ctx, with date, tasks, runs and model) only when it verifies andlean-ctx was faster without answering fewer tasks correctly. The figure
describes your suite, model and machine, not lean-ctx in general. No
surface estimates speed from live sessions.
Added — what lean-ctx did, in your editor and in signed savings batches
packages/vscode-lean-ctx,source only in this release; it is not yet published to an extension
registry): one quiet status bar item for the open project, for example
◆ −1.2M tok ⛨ 3.✓(counted) or≈(derived), plus the verified speed proof if you have one.
lean-ctx value.mode = offis set.context.
lean-ctx prompt-segment --json [--dir PATH]prints the segment, thelabelled breakdown, the speed proof, the directory to watch and the verify
command as a stable JSON contract (
schema: 1) for editor status bars.lean-ctx dashboardserves on your machine opens with Guards that fired.
commands blocked, paths outside the project blocked, and prompt-injection
patterns flagged.
lean-ctx value --all.signed security tally.
and last hash.
fails
lean-ctx savings verify-batch, which lists it.the tally keeps verifying batches as before.
Fixed — the
savings_footerdefault is documented asneversavings_footerdefaults toalways.The real default has always been
never, so no footer tokens are added totool output unless you turn it on.
Fixed —
init --helpprints help, and the rules name only tools the agent has (#1849)lean-ctx init --help(and-h) ran a fullinitinstead of printing thehelp text, so a user checking the options rewrote their shell and agent
configuration. Both flags now print the usage and change nothing.
tools/listhides. Withdisabled_tools = ["ctx_callgraph"]the agent wasstill told to use
ctx_callgraph. Withprefer_native_editor, or on anative-editor client, it was told "if denied, use ctx_patch". The guidance
is now built from the same profile,
disabled_toolslist and client rulesthat decide
tools/list, one profile per rules target. The parallel-callsand "ACTUALLY EMIT" lines mention
ctx_composeonly when the profile hasit, and the graph anti-pattern names only the graph tools still enabled.
apply_patch, so the line now says "the host's native edit tool". Rulesversion 11 rewrites the installed files on the next start. The Codex guide
no longer tells Codex to use a native
Globit does not have.Thanks to @skonebrant for the detailed report.
Fixed —
_lcshims and shell hooks survive package-manager installs and updates (#1851)lean-ctx init --globalwrote the_lc/_lc_compressPATH shims next tothe running binary. With scoop, Homebrew, npm or mise that is a versioned
directory off
PATH(scoop/apps/lean-ctx/3.10.2,Cellar/…,node_modules/…,mise/installs/…). The shims were therefore never found.Hosts that replay a shell snapshot without the
_lcfunction, such asClaude Code's Bash tool, kept the
alias git='_lc git'aliases, and everyaliased command failed with
_lc: command not found.PATH. Otherwise they go to thePATHdirectory holding thelean-ctxlauncher: the Homebrew symlink, the scoop shim, the npm or mise launcher.
~/.local/binis the last choice when it is onPATH, and the firstwritable directory wins.
lean-ctx uninstallalso removes shims from thePATHdirectories, and still removes only files carrying the lean-ctxmarker.
shell-hook.*andenv.shembedded the same versioned path, which stoppedexisting after
scoop update+scoop cleanuporbrew upgrade. They nowembed the stable
PATHlauncher when the binary is offPATH. Installswhose binary sits on
PATH(theinstall.shand Windows ZIP layouts) areunchanged. Run
lean-ctx init --globalonce after updating to rewrite thehook. Thanks to @ZacKienzle2 for the detailed report.
Fixed —
ctx_shellplaces a relative redirect after acdwhere it lands (#1850)teetarget in the call'scwd, even after acdearlier in the same command.cd /tmp/x && echo a > out.txtwas therefore refused, althoughout.txtlands in a scratchdirectory. With a scratch
cwd,cd <project> && echo a > fwas allowed,although
flands inside the project. Each command is now judged in thedirectory it actually runs in: the call's
cwd, moved by a plaincd <dir>that is certain to have run before it. When that directory cannotbe known, a relative target is refused, and the message asks for an
absolute path. That covers a
cdthrough a variable,pushd, acdin asubshell or a group, a
cdthat may fail or be skipped, and a backgroundedlist. Absolute targets are judged as before. Thanks to @andig for the report.
Fixed — the PowerShell tool no longer gets a Bash command back (#1848)
lean-ctx hook rewrite. The hook answered them the way it answers Bash:compounds and commands outside the shell allowlist came back wrapped as
'…\lean-ctx.exe' -c '…'. PowerShell cannot parse that(
Unexpected token '-c'), so every such call failed. Quoting alone would nothave fixed it:
lean-ctx -cruns its command in the shell lean-ctx detectsfor itself, which is Git Bash on most Windows machines, not PowerShell.
Get-Content,Select-StringorGet-ChildItem(and the other read, search and listcommands) becomes
& '…\lean-ctx.exe' read …, quoted for PowerShell.Windows paths such as
src\main.rskeep their backslashes. Everything elseruns in PowerShell unchanged. A word that PowerShell would evaluate first,
such as
$env:X,@args,a,b,*.mdor(…), is never rewritten.enforcemode a PowerShell command itblocks (for example
Remove-Item) is refused with the allowlist's reason,not wrapped.
warnandoffbehave as before.@and,, whichPowerShell treats as operators.
Fixed — a task overview no longer lists facts that share only a generic verb (#1832)
lean-ctx overview '<task>'listed any fact that shared a single word withthe task as a "relevant fact". A task like "Inspect alpha parser header
validation." therefore showed an unrelated fact such as "Inspect and review
gamma certificate deployment sequencing." because both contain "inspect".
Two unrelated tasks got the same unrelated facts. Only the task's content
words are matched now: words like "the", "and", "inspect", "review", "check"
or "fix" are ignored, punctuation is trimmed ("validation." matches
"validation") and words are split the way facts are indexed. A task made
only of such words lists no facts. The sub-agent briefing pack uses the same
matching. An explicit
ctx_knowledgerecall still matches every word youpass. Thanks to @rtbe for the isolated reproduction.
Fixed — Pi:
ctx_shell'stimeoutnow reacheslean-ctx -c(#1833)pi-lean-ctx,ctx_shell(command, timeout=<seconds>)passed the timeoutonly to Pi's outer bash tool. The
lean-ctx -cwrapper inside it kept itsdefault of 120 s. A call with
timeout=200was therefore stopped afterabout two minutes with
output truncated at 8 MB / 120s limit. Theper-call timeout is now passed to
lean-ctx -casLEAN_CTX_SHELL_TIMEOUT_MS, capped at the same one-hour ceiling the MCPtimeout_mshas. ALEAN_CTX_SHELL_TIMEOUT_MSyou set yourself still wins,and
raw=trueis unchanged because it does not go through lean-ctx. Thanksto @rtbe for the precise report.
Fixed — a
jqprogram in single quotes is no longer blocked assource(#1829)jqfilter such as'… | . as $r | …'was blocked. The block saidthe command runs
eval/exec/sourceor a substitution. The quickpre-scan for
| .and similar separators looked through quotes, so it readjq's identity filter as the shell's
.(source) builtin. That scan nowignores text inside single quotes. The per-segment check still decides
every command, so a real
.orsourceat command position is stillblocked, next to quotes too. The block message now names
sourceand..Thanks to @andig for the report and the reproductions.
Fixed — Claude Code's Bash sandbox no longer blocks every command (#1834)
sandbox.enabled, Claude Code spawns each Bash call asenv SANDBOX_RUNTIME=1 … /usr/bin/sandbox-exec -p '<profile>' /bin/zsh -c '<cmd>'through
zsh -c. The.zshenvredirect forwarded that launcher tolean-ctx -c, nothing recognised it, and the allowlist hard-blocked on theinner
eval— exit 126 for every command, evenecho.lean-ctx -cnowrecognises exactly the launcher Claude Code builds
(
/usr/bin/sandbox-exec -p <profile>followed by/bin/zsh,/bin/bash,/bin/shor the user's own$SHELL, then-c <script>). It runs theallowlist gate on the unwrapped script before starting the sandbox, then
spawns the launcher as is, so the command still runs inside the user's
sandbox. The launcher is never unwrapped, because that would run the
command outside the sandbox.
front of the launcher: the proxy and CA variables,
TMPDIR, and the exactGIT_SSH_COMMAND/GIT_CONFIG_*values. Every other variable name fallsback to the normal gate, as do
envoptions,-uof a hook variable,anything between
sandbox-execand the shell, relative paths, and Linuxbwrap. The normal gate still blocks these, as before. This includesuser-defined
sandbox.setEnvVarsand an inheritedJAVA_TOOL_OPTIONSwitha non-default value.
Fixed — npm install no longer breaks when the install path contains
$npm install lean-ctx-bininto a path such ascache_$HOME_binran thewrong command (#1838).
postinstall.jsbuilt shell strings like"<binary>" onboard, and a double-quoted$…is still expanded by the shell,so the binary path was rewritten before it ran.
onboardand the pre-installstopnow go throughexecFileSyncwith an argv array.curl download, the GitHub API lookup, the Windows
stopand the Windowstarextraction. None of them go through a shell anymore.
postinstall.dollar.test.cjsnow runs in CI next to the stdio test (skippedon Windows, where the shebang fixture cannot run).
Security — a rewritten Bash command no longer expands
$varsor runs`…`early (#1862)lean-ctx hook rewritebuilt the command it handed back to the host's Bashtool with double quotes in two places. On Windows, the
lean-ctx -c "…"wrapused the quoting of the shell lean-ctx detects for itself, which can be
cmd.exe. Git Bash then expanded
$HOMEand ran$(…)before lean-ctx sawthe command. On every platform, a word that the direct
read/grep/lsrewrites re-quoted, such as a single-quoted
'$HOME'pattern, came back as"$HOME"and was expanded as well.word now use single quotes.
lean-ctx -c 'git log --format="$HOME"'reacheslean-ctx unchanged.
$or`is no longer rewritten word by word, sincethe rewrite cannot tell
'$HOME'(literal) from"$HOME"(expand). It keepsthe agent's own quoting inside the
-cwrap, or runs unchanged where thereis no wrap (
cat).Security — the shell allowlist checks the command a wrapper really runs
command. For
env,sudo,doas,nice,timeoutandxargstheallowlist skipped each
-xword on its own, so the value that followed it(
-u NAME,-s SIGNAL,-I REPLACE, …) was checked in place of the commandthat actually runs, and that command never reached the allowlist or the
inline-code check. Each wrapper's options are now parsed with the argument
they take — attached or separate, short clusters, GNU long-option prefixes,
--,timeout's duration operand,env -Ssplit strings — and the checkapplies to the real command.
env -u HOME git statuswas blockedbecause
HOMElooked like the command. It is allowed now.commandandbuiltinrun the word after them, but as shell builtins theyskipped every check. They are now walked like the other wrappers;
command -v/-Vstill only look a name up.eval,execorsource, which stayblocked regardless of the allowlist.
are now refused.
dangerous-flag checks, not only the allowlist lookup.
Fixed — secret redaction stays on its line and leaves
*****masks alone (#1830, #1831)token:orpassword =, the blank after the separator also matched the linebreak, so the first word of the following line was treated as the value. In
a diff that was the
+/-marker; in YAML the nested key was replacedwhile its value stayed visible. All key/value rules, in both
ctx_readredaction and secret detection, now allow only spaces and tabs around the
separator;
BearerandAuthorization:likewise stay on their line.Reported by @andig (#1830).
password: *****were redacted. They are what aredactor writes in place of a secret, not a secret. Replacing them broke
fullreads as an edit source:replace_uniquebuilt from the view did notfind the text on disk. An all-asterisk value now counts as a placeholder.
Reported by @andig (#1831).
Fixed — lean-ctx builds on FreeBSD again, without relying on
renameat2engine_artifact/unix.rswithcannot find value result(#1828, reported with a patch by @yurivict).Engine artifacts are published with a rename that must never replace an
existing file; only Linux (
renameat2) and macOS (renameatx_np) had one,and the fallback branch for every other Unix did not compile.
renameat2by syscall number: FreeBSD only has itsince 16.0, and an unknown syscall on 14.x/15.x raises SIGSYS and kills the
process. Targets without a native no-replace rename now link the new name
(
linkatfails withEEXISTif it exists, on every POSIX system) and thenremove the temporary name. A file system without hard links is reported as
unsupported by the existing capability probe instead of failing mid-publish.
Fixed — the account commands are no longer hidden behind a research flag
login,register,sync,cloudandcontributeanswered "unavailable"unless
LEAN_CTX_EXPERIMENTAL_HOSTED=1was set. The flag was meant forunreleased research, but it also hid the optional account sync of existing
accounts. The flag is gone, and these commands work without it again. They
need an account; without one, lean-ctx keeps working locally as before.
cloud statusnames the signed-in account.directly and confirms that local context is unchanged, instead of a generic
upgrade pitch.
HOSTED RESEARCHis nowACCOUNT SYNCand lists thecommands that actually work.
Fixed — a rejected credential no longer looks like being offline
classify_outcomesspecial-cased only HTTP 402; an HTTP 401 fell through toNetworkFailure, which prints nothing and deliberately leaves the day's syncslot open so the next cycle retries. A machine whose API key had been
revoked therefore retried forever, in silence.
AutoSyncOutcome::Unauthenticated, ranked above the plan check: adead credential makes every other signal moot. It prints once per process,
says that local data is intact, and names the fix (
lean-ctx login).consumes_daily_slot, so "only anetwork failure leaves the slot open" is stated in one place and tested.
loginandregisternow send a device label with the request.Fixed — a
greppattern is no longer silently reinterpreted (#1827)PreToolUseshell hook rewrotegrepontolean-ctx grepwhilepassing the pattern through verbatim — but the two sides do not speak the
same regex dialect. Plain
grepapplies POSIX basic regular expressions,where
\|alternates and a bare|is a literal.lean-ctx grepcompileswith the Rust
regexcrate, which reads those exactly the other way round.grep -n "headroom\|HEADROOM" db.pyon afile containing both answered
0 matches for 'headroom\|HEADROOM' in 1 filesand exited 1. The same defect runs the other way too —
grep -n "a|b"is aliteral search in BRE, but the rewrite reported every line containing
aorb. Seven metacharacters flip meaning this way:|+?(){}.no error to notice, which is the shape that matters for anyone scripting
against the output.
grepinvocation whose pattern contains one of those seven now declinesthe rewrite and falls through to the
lean-ctx -cwrap, where the platform'sown grep resolves the pattern — the same escape valve
fgrepand thesemantic flags (
-i,-w,-F, …) already used. Output is stillcompressed; only the matching is handed back.
egrepandrgkeep the fast path: POSIX extended regular expressions andthe Rust
regexcrate agree on all seven. So does a plaingreppattern thatcontains none of them.
cause is platform-independent and reproduces identically on macOS and Linux.
Fixed — the proxy no longer forwards conversations it has emptied (#1789)
empty string. Upstream received only the system message, so the model
answered a conversation it could not see — HTTP 200 on both sides, no error
anywhere, which is what let it ship in two releases.
compress_live_prosepasses a task hint, which selects
CompressionStrategy::Aggressive, and thatstrategy drops any paragraph carrying neither a task nor a technical
keyword. Sound for a document, where the surviving sections still carry the
meaning; a chat turn is a single paragraph, so dropping its only section
deletes the message.
compress_textthen accepted the empty result because itwas shorter — exactly what a compressor is supposed to prefer.
detect_live_zonepins the frozenboundary to
last_system + 1. English technical conversations largely escapedtoo, since they happen to hit the hardcoded keyword list, which is why a
French-language report is what finally surfaced it.
snapshots before the pipeline runs, the pipeline's guard proves the cache
prefix stable (untouched by emptying the live zone), and the savings floor
only reverts compressions that save too little — deleting all content sails
through as a perfect saving.
CompressionStrategy::Light;compress_with_strategyrefuses to turnnon-empty input into empty output; and a new
destroys_contentinvariantreverts every stage when a message that arrived with text comes out empty,
covering both wire shapes (string content and block arrays). Tool output keeps
its section-dropping compressor, where whole-message deletion is not possible.
Fixed — a sub-agent is never served a cache stub it cannot resolve (#1801, #1804)
their first read of anything the parent had touched they got a
[cross-agent cache · … tokens avoided]orunchanged, already in contextstub instead of the data. Nothing in the stub signalled the loss, so an agent
that did not notice proceeded as if the file or directory were empty.
subagent_scoperesolves
proc:{pid}-{ts}once per process on the reasoning that "each MCPconnection is a separate stdio process" — but Claude Code sub-agents reuse the
parent's connection and spawn no lean-ctx process, so parent and sub-agent
resolve the identical scope. And the content-dedup ledger is process-global
and keyed on path alone;
check_contenttakes no session, conversation oragent argument at all.
multiple_conversations_recentalreadyestablished for concurrent chats: when a matching id cannot be trusted to name
this caller, nothing is provably in context, so no stub is served.
Re-delivering costs tokens; delivering a dangling reference costs the caller
its data.
task:and an explicitcustom:override — keep deduplicating, as does the legacy transcript path where one
daemon serves one conversation.
tools healthcannotreport savings that were not made.
the suite inside Claude Code resolved a
proc:scope for every test and 31 ofthem failed, while the same tests passed in CI where
CLAUDECODEis unset.Fixed — git decides corpus membership, not a leading dot (#1792)
lean-ctx findreturned nothing for a tracked dotfile or a tracked fileunder a hidden directory, while
ctx_globfound the very same paths. The BM25and graph corpus builders excluded them too, so
ctx_composeandctx_overviewanswered "no match" for code that was present and tracked — anincomplete index is indistinguishable from an empty result.
ctx_glob,ctx_searchand one of the two walks insearch_index.rsincluded hiddenpaths;
find, the other walk in that same file, BM25 and both graph walksexcluded them.
findhad no flag to change it, thoughlshas had--allall along.
walk_filterasSKIP_HIDDEN_IN_CONTENT_WALKwith itsreasoning attached. A leading dot is a display convention, not a relevance
signal —
.github/,.agents/and.config/routinely hold source-ownedautomation a repository genuinely tracks.
correctness bug, not a preference.
ctx_treestill hides dotfiles behind--all,because a listing rendered for a person is where that convention belongs.
.gitignorestilldecides, so an ignored dotfile stays excluded; and
keep_entrystill prunes.claude/.cursorand the other agent-copy directories (#1480).Fixed —
auto_capture = falseis enforced at the store, not at each producer (#1802)auto_capture = false, machine-derived facts kept appearing, anddeleting them from
knowledge.jsondid not help: one MCP call brought everyone back carrying its original
created_at. A curated store could not bekept clean — 11 curated facts against 150+ machine entries.
auto_capture::capture_findingchecksis_enabled();session::state::extract_session_factsdoes not, and it is reached fromsession::persistence::persist_session_facts— the session save path, whichis why a single tool call sufficed. The stale dates came from
auto_session_factcopying the finding's own timestamp: the facts werere-materialised from
sessions/<id>.json, not re-derived.add_factandremember.A check at a call site only covers the call sites that exist when it is
written — which is exactly how this defect arose. At the store, a producer
added later cannot bypass it.
refresh
last_confirmedon facts an earlier enabled run left behind; theywould otherwise look perpetually fresh and never age out.
MAX_FINDINGS, and backs handoff, recap and metrics — none of which this keyclaims to disable. Existing
auto:*facts are not deleted either; removingsomeone's data on a config flag is not this change's call. They can now be
deleted by hand and stay deleted, which before they did not.
Fixed —
ctx_shellno longer fails outright on Windows hosts that validate env names (#1799)ctx_shellcall failed before the command ran, withInvalid bash env name: "COMMONPROGRAMFILES(X86)".inherited environment through their spawn hook. Windows has carried
ProgramFiles(x86)/CommonProgramFiles(x86)since forever, and hostscommonly validate names against
^[A-Za-z_][A-Za-z0-9_]*$— the parenthesesare rejected, so the spawn was refused for every command, whether or not it
touched those variables.
ctx_shellworked on the same machine because it runsthrough lean-ctx's own executor and never hands the environment to a
validating host. That contrast is what located the defect in the wrapper
rather than in the engine.
both spawn hooks, since
rawbypasses lean-ctx but still spawns through thehost. Filtering here rather than in
leanCtxEnvkeeps the completeenvironment for the MCP bridge and the engine's executor, which do not
validate. The filter runs last over the merged result, so it also covers
config-supplied
forwardedEnv.$ProgramFiles(x86)by name anyway, so the value was only ever reachablethrough
env/printenv.Fixed — instruction files are classified by file, not by parent directory (#1794)
ctx_read(mode="map")on ordinary TypeScript under a skill directory wasoverridden to
fulland answered with a large, truncated dump. The callerlost both the structural map it asked for and the tail of the file, and the
workaround — guessing line windows — requires already knowing which sections
matter.
is_instruction_filematched on path substrings: any path containing/skills/,/.cursor/rules/or/.claude/rules/counted, whatever the filewas. A skill ships its instructions as documents and its implementation as
source, so
.ts,.py,.rsand.shbeneath one are ordinary code.inside an instruction directory only document extensions (md, mdc, markdown,
txt, rst, adoc) or no extension at all — rule files are routinely named
without one, and no language ships source that way.
lower.contains("/agents.md"), which the filenamematch already covers and which would additionally have matched a directory
named
agents.md.fullfor instruction files; thisfixes the classification that decided what an instruction file is.
Fixed —
ctx_grepbounds how wide a result line may be, not just how many (#1650)limit: 1withcontext: 0returned an entire minified JSON line — a~100 KB payload for one match, almost all of it unrelated content that
happened to share the line.
limitcaps how many matches come back; nothingcapped how wide each one is, and the only size guard was a 512 KB cap on the
whole output, far above any single line.
where the match sits, so it does the cutting:
-M/--max-columnswith--max-columns-preview. The truncation is therefore explicit in the outputrather than a silently short answer, which is what made the original behaviour
hard to notice.
path:line:is still printed, so the fullline is one
ctx_read(path, mode="lines:N-N")away. The newmaxLineCharsparameter raises the budget and
0removes it. 400 bytes comfortably fits areal source line while keeping a minified blob out of the context window.
search drops from 100042 to 434 bytes of output.
Fixed — the documented bare
-Ntail mode now works (#1813)-N=tailall along, but onlylines:-Nwas everimplemented.
mode="-3"failed to parse as a mode and was answered with thehead of the file — no header, no warning, no sign that the requested view was
not the one delivered.
lines 1..~140 and then the token cap, so the tail was unreachable through the
mode that exists to reach it, and the truncation notice made a wrong answer
look like a size problem.
ReadMode, which owns mode spelling, and canonicalizesto the single internal form. Both entry points call it — the MCP handler and
lean-ctx read— because fixing only the MCP path would have leftlean-ctx read --mode -3still answering with the head, and two surfacesdisagreeing about a documented mode is how this class of defect starts.
-<digits>payload is rewritten.-x,-3-5,--3and a bare-still reach the normal unknown-mode handling instead of being silentlyreinterpreted — the very failure this removes.
Fixed —
inlinegets the verbatim turn budget it shares withraw(#1812)ctx_shell(inline=true)truncated at ~4k tokens with no archive id, no pathand no
ctx_expandreference. The same command withraw=truereturned all4251 tokens. The tail was simply gone, and the notice pointed at a
file-oriented tool that needs a path command output does not have.
ordinary backstop made a documented recovery path unreachable above ~16 KB.
verbatim_requestedrecognisedraw = trueandmode = "raw"but notinline = true, although the schema calls that one "return verbatim outputinline" — the same request in different words.
answer: the archive line is produced on the compressed path, which
inlineskips by definition. Adding
inlineto the verbatim set fixes both halves —the output fits, so no cut happens and no recovery line is needed.
archive.inline_max_bytesis referencedonly in the schema description and read nowhere in the code, so the "larger
output uses the archive/firewall" half of that sentence is unimplemented.
Fixed — the
ctx_shellguard messages state the rule that actually fires (#1814, #1815)guards claimed
ctx_shellis read-only for project files, whilecp,mkdir,touch,rm -rf,git commitandgit worktree addall pass unblocked inthe same session. The rule that fires is narrower: no output capture (
>,>>,| tee, heredoc-write, download-to-file) into a project path, becausectx_shellcompresses what it returns and the captured bytes may not be thecommand's own (#1303).
cpthat
ctx_shellwould have run, and it offered a safety property that does nothold. All four messages now name the capture rule and say explicitly that other
commands are not restricted.
$varcommand word is a correct split, not a mis-split. The diagnosticblamed lean-ctx's parser, told the caller not to trust the split, suggested
re-quoting, and asked for a bug report — for behaviour working as designed;
$var-as-commandis listed under ANTIPATTERN in the tool description, and noquoting makes a variable command gateable. A
$-prefixed base now gets its ownmessage explaining that the name is only known at run time and naming the form
that works. The genuine mis-split guidance stays for tokens that really are not
command names (#1646), with a test pinning both halves.
Fixed — a relative redirect target is judged where the command actually runs (#1811)
target without resolving it.
cwd=<scratch> … > probe.txtwas blocked whilethe identical
> <scratch>/probe.txtwas allowed — same destination, oppositeverdicts, under a message naming a rule it had not applied.
narrows as often as it widens: a relative target under a project cwd resolves
into the project and is refused on the same rule as an absolute one, instead
of by accident of its spelling.
cwdargument. A jail-rejectedcwdis silently replaced with the project root, so judging the raw argumentwould let a caller name an out-of-project scratch dir, have the guard approve
> probe.txtagainst it, and then have the command run in the project root andwrite there.
ctx_shellresolves the run directory once and feeds the samevalue to the guard and to the run. Without a session the directory is unknown
and the guard keeps its stricter earlier refusal.
cwdis followed; an in-commandcdis not. Decidingthe effective directory from command text means getting
cd a && cd b,conditional and quoted forms all right, and an error there would grant a write
the guard means to refuse.
Fixed —
[[ … ]]is treated as a conditional construct, not a command (#1793)ctx_shellrejected ordinary read-only verification commands that used abash conditional, e.g.
git status --short; if [[ -n x ]]; then …→'[[' is not in the shell allowlist.[[was missingfrom
SHELL_BUILTINS, although its POSIX equivalentstestand[werealready members and it is bash conditional syntax evaluated by the shell
itself. And
split_on_operatorsshielded( … )and{ … }but had no notionof
[[ … ]], so the&&inside a condition split the conditional intofragments that resolve to no real command.
Without that, a glob character class (
ls a[[:alpha:]]) or an array subscriptwould open a depth that never closes and would shield the rest of the line from
splitting — an under-block, which this walker must never do.
run: a command after the conditional is still its own validated leaf, and a
substitution at command position stays hard-blocked.
Fixed — Codex is wired onto the rail its credential authenticates on (#1685)
install_shell_exportswrote
OPENAI_BASE_URL=…/v1into every shell rc unconditionally, whileinstall_codex_envdeliberately writes nothing for a ChatGPT-subscriptionlogin — that rail answers a subscription token with
401 … Missing scopes: api.responses.write, a message about organization rolesthat names nothing real. The environment overrode the config decision, so the
careful answer never took effect and the 401 was what users saw.
the only unconditional one. It is now gated the same way in all four shell
dialects, with an explanatory comment in place of the export so the omission is
visible rather than silent. The rule itself still lives in
codex_uses_chatgpt_login.is_codex_proxy_model_provider_entrycountedmodel_provider = "openai"asone of ours. lean-ctx writes
leanctx-chatgpt, neveropenai, so that branchcould only ever remove a pin the user had set themselves — silently, on every
setup pass, with no test covering it. The generated pin is still stripped, so
flipping the ChatGPT rail back off still restores native Codex history (#597).
Routing the subscription token to the rail it actually authenticates on
replaces that explanation with a working path.
CODEX_HOME, because the OpenAI line now depends onCodex's auth state, which
resolve_codex_dirotherwise reads from the real~/.codex— a test that tracked the developer's own login would assertnothing.
Fixed — the steering profiles say which tools the
ctx_*mapping governs (#1788)one line earlier, that the mapping "is NOT optional" — a claim with no stated
boundary. Read literally it swallows every other MCP server's tools, so an
agent that takes it seriously stops using tools l
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.