Skip to content

Update dependency github:yvgude/lean-ctx to v3.10.5 - #2313

Open
renovate[bot] wants to merge 2 commits into
mainfrom
renovate/github-yvgude-lean-ctx-3.x
Open

renovate[bot] wants to merge 2 commits into
mainfrom
renovate/github-yvgude-lean-ctx-3.x

Conversation

@renovate

@renovate renovate Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Type Update Change
github:yvgude/lean-ctx tools minor v3.8.8 → v3.10.5

Release Notes

yvgude/lean-ctx (github:yvgude/lean-ctx)

v3.10.5

Compare Source

Fixed — MCP configs survive package-manager updates
  • Scoop, Homebrew, npm and mise run lean-ctx from a versioned directory that
    the next update removes. setup, init, doctor --fix and wrap wrote
    that directory into every agent's MCP command and hooks, so after an
    update the agent pointed at a deleted binary (#​1873). They now write the
    stable launcher on PATH (the Scoop shim, the Homebrew symlink), as the
    shell hooks already did since #​1851. lean-ctx doctor reports an old
    versioned path as drift, and lean-ctx doctor --fix rewrites it.
Fixed — ctx_search query= searches the session's project
  • With project_root pinned in config.toml (or LEAN_CTX_PROJECT_ROOT),
    the BM25 form of ctx_search (query=, action=semantic, and the
    "Ranked files" section of ctx_compose) searched the pinned project's
    corpus from any session, while action=regex and action=symbol searched
    the 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_shell calls all get their answer
  • Two or more identical foreground ctx_shell calls issued at once share one
    job. 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
  • With shell_security = "warn", a command outside the allowlist runs, but
    the 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 --help prints real line breaks
  • The option list printed literal \n\ sequences on one line. It is now one
    option per line.
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-ctx

Note: After upgrading via cargo/npm/brew, run lean-ctx setup to refresh shell aliases. lean-ctx update does this automatically.

Full Changelog: yvgude/lean-ctx@v3.10.4...v3.10.5

v3.10.4

Compare Source

Fixed — lean-ctx update --help no longer installs an update
  • lean-ctx update --help (and -h, and the same flags on enable-gpu)
    now print the command's options. Before, the flag was ignored and the
    command downloaded and installed the latest release.
  • An unknown option, for example a typo like --chek, is refused with exit
    code 2 instead of running an update. --force and --rewire are still
    accepted, and still have no effect, because older instructions mention them.
Fixed — the value status line now reaches existing Claude Code installs
  • 3.10.3 added lean-ctx's status line (◆ lean-ctx −13.1K tok) to Claude
    Code, but it was only written by lean-ctx init --agent claude. Installs
    that were updated, or set up with lean-ctx setup, never got it. The hook
    refresh 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".
  • A "statusLine": null entry in Claude's settings.json is now replaced.
    Before, lean-ctx reported the status line as set but wrote nothing.
  • Turn recaps now appear in a normal session: every 5 turns when the window
    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_tokens settings are kept.
Fixed — OpenCode, Windows paths and allowlist noise
  • OpenCode: shadow mode now sets read, grep, glob and bash to "ask"
    instead of "deny" in opencode.json (#​1864). OpenCode removes a denied
    tool from the request, and the OpenCode Zen free tier refuses requests
    without bash and read with a 403. lean-ctx setup rewrites the
    "deny" entries older releases wrote; uninstall removes both values.
  • The rewritten lean-ctx read/grep/ls commands now quote the lean-ctx
    path. Before, a binary under a path with a space, such as
    C:\Program Files\..., was split by the shell and the command failed with
    exit 127 (#​1865).
  • Warn-only allowlist findings (command substitution, pipe into an
    interpreter) are logged at info instead of warn, so they no longer show
    up on stderr of every command. Log output carries ANSI colour codes only
    when stderr is a terminal (#​1866).
  • git --version | head -1; python --version is no longer reported as a pipe
    into a bare interpreter. Only the command that actually receives the pipe is
    checked (#​1867).
Fixed — ctx_shell can no longer suspend the agent in your terminal
  • A command run through ctx_shell now starts in its own session without a
    controlling terminal. Before, an interactive shell somewhere below it (a
    test suite that runs zsh -i or bash -i, for example) took over the
    terminal, and the terminal stopped Claude Code: zsh printed
    suspended (tty input) claude and the terminal was left in mouse mode.
    Commands that need a terminal now fail right away instead of freezing
    the agent.
  • lean-ctx's own prompt-integration tests no longer take over the terminal
    when cargo test runs in one.
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-ctx

Note: After upgrading via cargo/npm/brew, run lean-ctx setup to refresh shell aliases. lean-ctx update does this automatically.

Full Changelog: yvgude/lean-ctx@v3.10.3...v3.10.4

v3.10.3

Compare Source

Added — lean-ctx value: what lean-ctx did, with proof
  • lean-ctx value shows the tokens kept out of the model's context and the
    security events of the current session. --session <id> picks another
    session, --all covers every session, and --json prints the result as
    JSON. 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:

    • a secret redacted from tool output (counted once, on the output that
      reaches the model)
    • a shell command blocked by the allowlist
    • a path refused by the project jail

    The entry records the kind, the count and the session.

  • New [value_display] config section (mode = off | minimal | milestones | verbose, default minimal, env LEAN_CTX_VALUE_DISPLAY). It controls the
    value 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
  • Status line. lean-ctx statusline prints one dim line for Claude Code's
    statusLine, for example ◆ lean-ctx −1.2M tok · 41 cached · ⛨ 3 (⛨ counts
    security 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 claude sets it only when there is no status
    line yet, or when the existing one is already lean-ctx's. If you have your
    own status line, it stays untouched and init prints the command that
    chains it: lean-ctx statusline --wrap '<your command>'. The wrapped
    command 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.
  • Turn recap. Every 10 turns (recap_every_turns), the Stop hook shows a
    one-line systemMessage such 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 keeps
    growing until it is worth a line.
  • Session recap and weekly digest. On a fresh start, SessionStart shows
    the last session once, with its share of tool input
    (◆ lean-ctx · last session (62% of tool input): −1.4M tokens). Once a
    week it shows a digest of all sessions instead. Nothing is shown on
    resume, compact or clear.
  • These lines are systemMessage and status-line output. They never reach
    the 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 = off turns
    all of it off, and lean-ctx value proves every number.
Added — what lean-ctx did, in your prompt, your desktop and your commits
  • Prompt segment. lean-ctx init --prompt adds a dim segment such as
    ◆ −1.2M tok ⛨ 3 for the project you are in. It goes on the right in zsh
    and fish, and in front of PS1 in bash. The segment has its own rc block,
    your prompt stays as it is, and init --prompt off or lean-ctx uninstall
    removes it. Under Starship, init prints a custom module instead of
    editing rc files. lean-ctx prompt-segment --shell plain serves any other
    prompt engine. It reads one small snapshot file and prints nothing when
    there is nothing current to show.
  • Milestone notifications with mode = milestones: a desktop notification
    the 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.
    • At most one a day, and of a ladder only the highest new rung is shown.
    • Milestones are computed from the verified chains. A chain that fails
      verification triggers none.
    • Every notification ends with Proof: lean-ctx value --all.
    • They use osascript, notify-send or a Windows toast, with no new
      dependency. The text is passed as arguments, never as script.
  • Commit trailer. lean-ctx init --git-trailer installs a
    prepare-commit-msg hook that adds lean-ctx: 840.0K tokens saved, 1 secret kept out of context to a commit message.
    • It counts only what happened in this project since the last trailered
      commit.
    • It skips merges, squashes and amends.
    • It can never fail a commit, and it leaves an existing hook untouched
      (printing the line to add instead).
  • Wrapped security section. gain --wrapped and its compact form list the
    period's security events, re-counted from the audit trail. They appear only
    when the trail verifies.
  • The guide Seeing what lean-ctx did covers
    every surface, including Starship and Powerlevel10k.
Added — lean-ctx prove speed: a signed measurement instead of a speed claim
  • lean-ctx prove speed --suite <file> asks your model each task of an eval
    suite twice per run: once with a raw context dump, once with lean-ctx's
    context, both within the same token budget (--budget, default 4000). It
    needs a live, OpenAI-compatible endpoint (LEAN_CTX_EVAL_MODEL_URL,
    LEAN_CTX_EVAL_MODEL, optional LEAN_CTX_EVAL_MODEL_KEY), local Ollama
    included. Recorded responses are refused, since they have no latency.
  • The measurement is built to be fair: one warm-up request is not counted, the
    arm that goes first alternates per task and round, and each arm's latency is
    the median of --runs rounds (default 3). Every answer is scored, so a
    faster but worse result is reported as worse.
  • The result is a signed proof (<data_dir>/value/speed/, --out for a
    copy, --json to print it). prove speed --verify [FILE] re-checks the
    signature and recomputes the summary from the raw timings. A modified proof
    prints TAMPERED and exits 1.
  • gain --wrapped quotes your latest proof (<N>% faster model answers with lean-ctx, with date, tasks, runs and model) only when it verifies and
    lean-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
  • VS Code, Cursor and Windsurf extension (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.
    • Hovering it shows the breakdown, each line labelled ✓ (counted) or ≈
      (derived), plus the verified speed proof if you have one.
    • Clicking it runs lean-ctx value.
    • It is hidden when nothing was measured, the numbers are stale, or
      mode = off is set.
    • The extension computes nothing itself and never writes to the model's
      context.
  • lean-ctx prompt-segment --json [--dir PATH] prints the segment, the
    labelled breakdown, the speed proof, the directory to watch and the verify
    command as a stable JSON contract (schema: 1) for editor status bars.
  • Local Protection view: the Protection page that lean-ctx dashboard
    serves on your machine opens with Guards that fired.
    • It shows the lifetime counts of secrets kept out of context, risky
      commands blocked, paths outside the project blocked, and prompt-injection
      patterns flagged.
    • Every count is re-derived from the local audit trail, the same as
      lean-ctx value --all.
    • It shows whether the trail is intact, or where the chain breaks.
  • Signed savings batches: a signed savings batch now carries a separately
    signed security tally.
    • The tally holds the counts, the audit trail's entry count, and its first
      and last hash.
    • It is bound to the batch's last entry hash, so a copied or edited tally
      fails lean-ctx savings verify-batch, which lists it.
    • The batch's own signature is unchanged, so a verifier that does not read
      the tally keeps verifying batches as before.
Fixed — the savings_footer default is documented as never
  • The config schema and reference said savings_footer defaults to always.
    The real default has always been never, so no footer tokens are added to
    tool output unless you turn it on.
Fixed — init --help prints help, and the rules name only tools the agent has (#​1849)
  • lean-ctx init --help (and -h) ran a full init instead of printing the
    help text, so a user checking the options rewrote their shell and agent
    configuration. Both flags now print the usage and change nothing.
  • The session instructions and the injected rules files listed tools that
    tools/list hides. With disabled_tools = ["ctx_callgraph"] the agent was
    still told to use ctx_callgraph. With prefer_native_editor, or on a
    native-editor client, it was told "if denied, use ctx_patch". The guidance
    is now built from the same profile, disabled_tools list and client rules
    that decide tools/list, one profile per rules target. The parallel-calls
    and "ACTUALLY EMIT" lines mention ctx_compose only when the profile has
    it, and the graph anti-pattern names only the graph tools still enabled.
  • The shadow-mode edit line said "native Edit/StrReplace". Codex edits with
    apply_patch, so the line now says "the host's native edit tool". Rules
    version 11 rewrites the installed files on the next start. The Codex guide
    no longer tells Codex to use a native Glob it does not have.
    Thanks to @​skonebrant for the detailed report.
Fixed — _lc shims and shell hooks survive package-manager installs and updates (#​1851)
  • lean-ctx init --global wrote the _lc/_lc_compress PATH shims next to
    the 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 _lc function, such as
    Claude Code's Bash tool, kept the alias git='_lc git' aliases, and every
    aliased command failed with _lc: command not found.
  • The shims now go to the running binary's directory only when it is on
    PATH. Otherwise they go to the PATH directory holding the lean-ctx
    launcher: the Homebrew symlink, the scoop shim, the npm or mise launcher.
    ~/.local/bin is the last choice when it is on PATH, and the first
    writable directory wins. lean-ctx uninstall also removes shims from the
    PATH directories, and still removes only files carrying the lean-ctx
    marker.
  • shell-hook.* and env.sh embedded the same versioned path, which stopped
    existing after scoop update + scoop cleanup or brew upgrade. They now
    embed the stable PATH launcher when the binary is off PATH. Installs
    whose binary sits on PATH (the install.sh and Windows ZIP layouts) are
    unchanged. Run lean-ctx init --global once after updating to rewrite the
    hook. Thanks to @​ZacKienzle2 for the detailed report.
Fixed — ctx_shell places a relative redirect after a cd where it lands (#​1850)
  • The write guard placed a relative redirect or tee target in the call's
    cwd, even after a cd earlier in the same command. cd /tmp/x && echo a > out.txt was therefore refused, although out.txt lands in a scratch
    directory. With a scratch cwd, cd <project> && echo a > f was allowed,
    although f lands inside the project. Each command is now judged in the
    directory it actually runs in: the call's cwd, moved by a plain
    cd <dir> that is certain to have run before it. When that directory cannot
    be known, a relative target is refused, and the message asks for an
    absolute path. That covers a cd through a variable, pushd, a cd in a
    subshell or a group, a cd that may fail or be skipped, and a backgrounded
    list. Absolute targets are judged as before. Thanks to @​andig for the report.
Fixed — the PowerShell tool no longer gets a Bash command back (#​1848)
  • On Windows, Claude Code and Copilot CLI send PowerShell tool calls through
    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 not
    have fixed it: lean-ctx -c runs its command in the shell lean-ctx detects
    for itself, which is Git Bash on most Windows machines, not PowerShell.
  • PowerShell calls now take their own path. A single Get-Content,
    Select-String or Get-ChildItem (and the other read, search and list
    commands) becomes & '…\lean-ctx.exe' read …, quoted for PowerShell.
    Windows paths such as src\main.rs keep their backslashes. Everything else
    runs in PowerShell unchanged. A word that PowerShell would evaluate first,
    such as $env:X, @args, a,b, *.md or (…), is never rewritten.
  • The shell allowlist still applies. In enforce mode a PowerShell command it
    blocks (for example Remove-Item) is refused with the allowlist's reason,
    not wrapped. warn and off behave as before.
  • PowerShell quoting elsewhere in lean-ctx now also quotes @ and ,, which
    PowerShell treats as operators.
  • Thanks to @​ZacKienzle2 for the precise root-cause analysis.
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 with
    the 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_knowledge recall still matches every word you
    pass. Thanks to @​rtbe for the isolated reproduction.
Fixed — Pi: ctx_shell's timeout now reaches lean-ctx -c (#​1833)
  • In pi-lean-ctx, ctx_shell(command, timeout=<seconds>) passed the timeout
    only to Pi's outer bash tool. The lean-ctx -c wrapper inside it kept its
    default of 120 s. A call with timeout=200 was therefore stopped after
    about two minutes with output truncated at 8 MB / 120s limit. The
    per-call timeout is now passed to lean-ctx -c as
    LEAN_CTX_SHELL_TIMEOUT_MS, capped at the same one-hour ceiling the MCP
    timeout_ms has. A LEAN_CTX_SHELL_TIMEOUT_MS you set yourself still wins,
    and raw=true is unchanged because it does not go through lean-ctx. Thanks
    to @​rtbe for the precise report.
Fixed — a jq program in single quotes is no longer blocked as source (#​1829)
  • A quoted jq filter such as '… | . as $r | …' was blocked. The block said
    the command runs eval/exec/source or a substitution. The quick
    pre-scan for | . and similar separators looked through quotes, so it read
    jq's identity filter as the shell's . (source) builtin. That scan now
    ignores text inside single quotes. The per-segment check still decides
    every command, so a real . or source at command position is still
    blocked, next to quotes too. The block message now names source and ..
    Thanks to @​andig for the report and the reproductions.
Fixed — Claude Code's Bash sandbox no longer blocks every command (#​1834)
  • With sandbox.enabled, Claude Code spawns each Bash call as
    env SANDBOX_RUNTIME=1 … /usr/bin/sandbox-exec -p '<profile>' /bin/zsh -c '<cmd>'
    through zsh -c. The .zshenv redirect forwarded that launcher to
    lean-ctx -c, nothing recognised it, and the allowlist hard-blocked on the
    inner eval — exit 126 for every command, even echo. lean-ctx -c now
    recognises exactly the launcher Claude Code builds
    (/usr/bin/sandbox-exec -p <profile> followed by /bin/zsh, /bin/bash,
    /bin/sh or the user's own $SHELL, then -c <script>). It runs the
    allowlist 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.
  • Only the environment variables Claude Code itself sets are accepted in
    front of the launcher: the proxy and CA variables, TMPDIR, and the exact
    GIT_SSH_COMMAND/GIT_CONFIG_* values. Every other variable name falls
    back to the normal gate, as do env options, -u of a hook variable,
    anything between sandbox-exec and the shell, relative paths, and Linux
    bwrap. The normal gate still blocks these, as before. This includes
    user-defined sandbox.setEnvVars and an inherited JAVA_TOOL_OPTIONS with
    a non-default value.
Fixed — npm install no longer breaks when the install path contains $
  • npm install lean-ctx-bin into a path such as cache_$HOME_bin ran the
    wrong command
    (#​1838). postinstall.js built shell strings like
    "<binary>" onboard, and a double-quoted $… is still expanded by the shell,
    so the binary path was rewritten before it ran. onboard and the pre-install
    stop now go through execFileSync with an argv array.
  • The same applies to the remaining commands that carried a path or URL: the
    curl download, the GitHub API lookup, the Windows stop and the Windows tar
    extraction. None of them go through a shell anymore.
  • postinstall.dollar.test.cjs now runs in CI next to the stdio test (skipped
    on Windows, where the shebang fixture cannot run).
Security — a rewritten Bash command no longer expands $vars or runs `…` early (#​1862)
  • lean-ctx hook rewrite built the command it handed back to the host's Bash
    tool with double quotes in two places. On Windows, the lean-ctx -c "…" wrap
    used the quoting of the shell lean-ctx detects for itself, which can be
    cmd.exe. Git Bash then expanded $HOME and ran $(…) before lean-ctx saw
    the command. On every platform, a word that the direct read/grep/ls
    rewrites re-quoted, such as a single-quoted '$HOME' pattern, came back as
    "$HOME" and was expanded as well.
  • The Bash tool's shell is POSIX everywhere, so the wrap and every re-quoted
    word now use single quotes. lean-ctx -c 'git log --format="$HOME"' reaches
    lean-ctx unchanged.
  • A command containing $ or ` is no longer rewritten word by word, since
    the rewrite cannot tell '$HOME' (literal) from "$HOME" (expand). It keeps
    the agent's own quoting inside the -c wrap, or runs unchanged where there
    is no wrap (cat).
  • PowerShell tool calls are unaffected; they have their own path (#​1848).
  • Thanks to @​rickgoud for the report.
Security — the shell allowlist checks the command a wrapper really runs
  • An option value of a delegation wrapper was taken for the delegated
    command.
    For env, sudo, doas, nice, timeout and xargs the
    allowlist skipped each -x word on its own, so the value that followed it
    (-u NAME, -s SIGNAL, -I REPLACE, …) was checked in place of the command
    that 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 -S split strings — and the check
    applies to the real command.
  • The side effect ran the other way too: env -u HOME git status was blocked
    because HOME looked like the command. It is allowed now.
  • command and builtin run the word after them, but as shell builtins they
    skipped every check. They are now walked like the other wrappers;
    command -v/-V still only look a name up.
  • A wrapper can no longer reach eval, exec or source, which stay
    blocked regardless of the allowlist.
  • Wrappers nested more than three deep used to end the check silently; they
    are now refused.
  • Commands inside a shell function body now get the inline-code and
    dangerous-flag checks, not only the allowlist lookup.
Fixed — secret redaction stays on its line and leaves ***** masks alone (#​1830, #​1831)
  • A keyword with no value redacted the next line. When a line ended in
    token: or password =, the blank after the separator also matched the line
    break, 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 replaced
    while its value stayed visible. All key/value rules, in both ctx_read
    redaction and secret detection, now allow only spaces and tabs around the
    separator; Bearer and Authorization: likewise stay on their line.
    Reported by @​andig (#​1830).
  • Asterisk masks such as password: ***** were redacted. They are what a
    redactor writes in place of a secret, not a secret. Replacing them broke
    full reads as an edit source: replace_unique built from the view did not
    find 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 renameat2
  • The FreeBSD build stopped at engine_artifact/unix.rs with
    cannot 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.
  • The fix does not call renameat2 by syscall number: FreeBSD only has it
    since 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
    (linkat fails with EEXIST if it exists, on every POSIX system) and then
    remove 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, cloud and contribute answered "unavailable"
    unless LEAN_CTX_EXPERIMENTAL_HOSTED=1 was set.
    The flag was meant for
    unreleased 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 status names the signed-in account.
  • If your plan does not include synchronization, the message now says so
    directly and confirms that local context is unchanged, instead of a generic
    upgrade pitch.
  • The help section HOSTED RESEARCH is now ACCOUNT SYNC and lists the
    commands that actually work.
Fixed — a rejected credential no longer looks like being offline
  • Background sync treated a rejected credential as a network problem.
    classify_outcomes special-cased only HTTP 402; an HTTP 401 fell through to
    NetworkFailure, which prints nothing and deliberately leaves the day's sync
    slot open so the next cycle retries. A machine whose API key had been
    revoked therefore retried forever, in silence.
  • Added AutoSyncOutcome::Unauthenticated, ranked above the plan check: a
    dead credential makes every other signal moot. It prints once per process,
    says that local data is intact, and names the fix (lean-ctx login).
  • The slot rule is now the named predicate consumes_daily_slot, so "only a
    network failure leaves the slot open" is stated in one place and tested.
  • login and register now send a device label with the request.
Fixed — a grep pattern is no longer silently reinterpreted (#​1827)
  • The PreToolUse shell hook rewrote grep onto lean-ctx grep while
    passing the pattern through verbatim — but the two sides do not speak the
    same regex dialect.
    Plain grep applies POSIX basic regular expressions,
    where \| alternates and a bare | is a literal. lean-ctx grep compiles
    with the Rust regex crate, which reads those exactly the other way round.
  • The report was a false negative: grep -n "headroom\|HEADROOM" db.py on a
    file containing both answered 0 matches for 'headroom\|HEADROOM' in 1 files
    and exited 1. The same defect runs the other way too — grep -n "a|b" is a
    literal search in BRE, but the rewrite reported every line containing a or
    b. Seven metacharacters flip meaning this way: | + ? ( ) { }.
  • Both directions produced a wrong answer that looks like a right one, with
    no error to notice, which is the shape that matters for anyone scripting
    against the output.
  • A grep invocation whose pattern contains one of those seven now declines
    the rewrite and falls through to the lean-ctx -c wrap, where the platform's
    own grep resolves the pattern — the same escape valve fgrep and the
    semantic flags (-i, -w, -F, …) already used. Output is still
    compressed; only the matching is handed back.
  • egrep and rg keep the fast path: POSIX extended regular expressions and
    the Rust regex crate agree on all seven. So does a plain grep pattern that
    contains none of them.
  • Not a Windows defect. The report came from Git Bash on Windows 11, but the
    cause is platform-independent and reproduces identically on macOS and Linux.
Fixed — the proxy no longer forwards conversations it has emptied (#​1789)
  • On every forwarded route the proxy replaced each live-zone message with an
    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.
  • The cause is a category error, not a faulty compressor. compress_live_prose
    passes a task hint, which selects CompressionStrategy::Aggressive, and that
    strategy 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_text then accepted the empty result because it
    was shorter — exactly what a compressor is supposed to prefer.
  • Only the system turn survived, because detect_live_zone pins the frozen
    boundary to last_system + 1. English technical conversations largely escaped
    too, since they happen to hit the hardcoded keyword list, which is why a
    French-language report is what finally surfaced it.
  • None of the three existing guards could observe this: the determinism guard
    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.
  • Fixed at three levels: conversation turns are pinned to
    CompressionStrategy::Light; compress_with_strategy refuses to turn
    non-empty input into empty output; and a new destroys_content invariant
    reverts 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)
  • Sub-agents silently received references to content they had never seen. On
    their first read of anything the parent had touched they got a
    [cross-agent cache · … tokens avoided] or unchanged, already in context
    stub 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.
  • Two layers rested on one premise that is false in Claude Code. subagent_scope
    resolves proc:{pid}-{ts} once per process on the reasoning that "each MCP
    connection 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_content takes no session, conversation or
    agent argument at all.
  • Both now fail closed, reusing the rule multiple_conversations_recent already
    established 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.
  • Scopes that do name one agent — Cursor's task: and an explicit custom:
    override — keep deduplicating, as does the legacy transcript path where one
    daemon serves one conversation.
  • Withheld stubs are recorded as misses, not hits, so tools health cannot
    report savings that were not made.
  • Tests also stop inheriting the developer's ambient agent environment: running
    the suite inside Claude Code resolved a proc: scope for every test and 31 of
    them failed, while the same tests passed in CI where CLAUDECODE is unset.
Fixed — git decides corpus membership, not a leading dot (#​1792)
  • lean-ctx find returned nothing for a tracked dotfile or a tracked file
    under a hidden directory, while ctx_glob found the very same paths. The BM25
    and graph corpus builders excluded them too, so ctx_compose and
    ctx_overview answered "no match" for code that was present and tracked — an
    incomplete index is indistinguishable from an empty result.
  • The walkers disagreed with no stated rule between them: ctx_glob,
    ctx_search and one of the two walks in search_index.rs included hidden
    paths; find, the other walk in that same file, BM25 and both graph walks
    excluded them. find had no flag to change it, though ls has had --all
    all along.
  • The rule now lives in walk_filter as SKIP_HIDDEN_IN_CONTENT_WALK with its
    reasoning attached. A leading dot is a display convention, not a relevance
    signal — .github/, .agents/ and .config/ routinely hold source-owned
    automation a repository genuinely tracks.
  • Deliberately not a config key: a corpus that silently omits tracked files is a
    correctness bug, not a preference.
  • Scope is content walks only. ctx_tree still hides dotfiles behind --all,
    because a listing rendered for a person is where that convention belongs.
  • Two boundaries verified rather than assumed, both tested: .gitignore still
    decides, so an ignored dotfile stays excluded; and keep_entry still prunes
    .claude / .cursor and the other agent-copy directories (#​1480).
Fixed — auto_capture = false is enforced at the store, not at each producer (#​1802)
  • With auto_capture = false, machine-derived facts kept appearing, and
    deleting them from knowledge.json did not help: one MCP call brought every
    one back carrying its original created_at. A curated store could not be
    kept clean — 11 curated facts against 150+ machine entries.
  • There are two producers of automatic facts and the flag reached only one.
    auto_capture::capture_finding checks is_enabled();
    session::state::extract_session_facts does not, and it is reached from
    session::persistence::persist_session_facts — the session save path, which
    is why a single tool call sufficed. The stale dates came from
    auto_session_fact copying the finding's own timestamp: the facts were
    re-materialised from sessions/<id>.json, not re-derived.
  • The guard now sits at the store's ingestion points, add_fact and remember.
    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.
  • The refusal sits before the coalescing branch, so a disabled run cannot even
    refresh last_confirmed on facts an earlier enabled run left behind; they
    would otherwise look perpetually fresh and never age out.
  • Session state is deliberately untouched: it is ephemeral, capped at
    MAX_FINDINGS, and backs handoff, recap and metrics — none of which this key
    claims to disable. Existing auto:* facts are not deleted either; removing
    someone'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_shell no longer fails outright on Windows hosts that validate env names (#​1799)
  • Every ctx_shell call failed before the command ran, with
    Invalid bash env name: "COMMONPROGRAMFILES(X86)".
  • The Pi extension builds its own bash tool instances and forwards the whole
    inherited environment through their spawn hook. Windows has carried
    ProgramFiles(x86) / CommonProgramFiles(x86) since forever, and hosts
    commonly validate names against ^[A-Za-z_][A-Za-z0-9_]*$ — the parentheses
    are rejected, so the spawn was refused for every command, whether or not it
    touched those variables.
  • The engine's MCP ctx_shell worked on the same machine because it runs
    through 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.
  • Names outside the POSIX identifier shape are now dropped at that boundary, in
    both spawn hooks, since raw bypasses lean-ctx but still spawns through the
    host. Filtering here rather than in leanCtxEnv keeps the complete
    environment 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.
  • Dropping these names costs nothing in practice: a POSIX shell cannot expand
    $ProgramFiles(x86) by name anyway, so the value was only ever reachable
    through 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 was
    overridden to full
    and answered with a large, truncated dump. The caller
    lost 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_file matched on path substrings: any path containing
    /skills/, /.cursor/rules/ or /.claude/rules/ counted, whatever the file
    was. A skill ships its instructions as documents and its implementation as
    source, so .ts, .py, .rs and .sh beneath one are ordinary code.
  • Classification is now by file: instruction documents by name anywhere, and
    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.
  • Also drops the redundant lower.contains("/agents.md"), which the filename
    match already covers and which would additionally have matched a directory
    named agents.md.
  • #​1584 fixed a bounded mode being widened to full for instruction files; this
    fixes the classification that decided what an instruction file is.
Fixed — ctx_grep bounds how wide a result line may be, not just how many (#​1650)
  • limit: 1 with context: 0 returned an entire minified JSON line — a
    ~100 KB payload for one match, almost all of it unrelated content that
    happened to share the line. limit caps how many matches come back; nothing
    capped how wide each one is, and the only size guard was a 512 KB cap on the
    whole output, far above any single line.
  • ripgrep bounds this natively and, unlike a slice applied afterwards, knows
    where the match sits, so it does the cutting: -M/--max-columns with
    --max-columns-preview. The truncation is therefore explicit in the output
    rather than a silently short answer, which is what made the original behaviour
    hard to notice.
  • Recovery needs no new machinery: path:line: is still printed, so the full
    line is one ctx_read(path, mode="lines:N-N") away. The new maxLineChars
    parameter raises the budget and 0 removes it. 400 bytes comfortably fits a
    real source line while keeping a minified blob out of the context window.
  • Measured against the reporter's fixture (a 100 KB single-line JSON): the same
    search drops from 100042 to 434 bytes of output.
Fixed — the documented bare -N tail mode now works (#​1813)
  • The schema has advertised -N=tail all along, but only lines:-N was ever
    implemented.
    mode="-3" failed to parse as a mode and was answered with the
    head of the file — no header, no warning, no sign that the requested view was
    not the one delivered.
  • That is the worst of the available answers. On a 165-line file the reporter got
    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.
  • The rule lives beside ReadMode, which owns mode spelling, and canonicalizes
    to the single internal form. Both entry points call it — the MCP handler and
    lean-ctx read — because fixing only the MCP path would have left
    lean-ctx read --mode -3 still answering with the head, and two surfaces
    disagreeing about a documented mode is how this class of defect starts.
  • Only a pure -<digits> payload is rewritten. -x, -3-5, --3 and a bare
    - still reach the normal unknown-mode handling instead of being silently
    reinterpreted — the very failure this removes.
Fixed — inline gets the verbatim turn budget it shares with raw (#​1812)
  • ctx_shell(inline=true) truncated at ~4k tokens with no archive id, no path
    and no ctx_expand reference.
    The same command with raw=true returned all
    4251 tokens. The tail was simply gone, and the notice pointed at a
    file-oriented tool that needs a path command output does not have.
  • #​1582 gave verbatim requests the larger turn budget precisely because the
    ordinary backstop made a documented recovery path unreachable above ~16 KB.
    verbatim_requested recognised raw = true and mode = "raw" but not
    inline = true, although the schema calls that one "return verbatim output
    inline" — the same request in different words.
  • The omission also removed the recovery route rather than merely shortening the
    answer: the archive line is produced on the compressed path, which inline
    skips by definition. Adding inline to the verbatim set fixes both halves —
    the output fits, so no cut happens and no recovery line is needed.
  • Recorded because it is still true: archive.inline_max_bytes is referenced
    only 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_shell guard messages state the rule that actually fires (#​1814, #​1815)
  • "never modifies project files" was false. The write-redirect and tee
    guards claimed ctx_shell is read-only for project files, while cp, mkdir,
    touch, rm -rf, git commit and git worktree add all pass unblocked in
    the same session. The rule that fires is narrower: no output capture (>,
    >>, | tee, heredoc-write, download-to-file) into a project path, because
    ctx_shell compresses what it returns and the captured bytes may not be the
    command's own (#​1303).
  • The overclaim cost twice over: it sent callers to native Write for a plain cp
    that ctx_shell would have run, and it offered a safety property that does not
    hold. All four messages now name the capture rule and say explicitly that other
    commands are not restricted.
  • A $var command word is a correct split, not a mis-split. The diagnostic
    blamed 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-command is listed under ANTIPATTERN in the tool description, and no
    quoting makes a variable command gateable. A $-prefixed base now gets its own
    message 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)
  • The write guard says "the destination decides", then refused every relative
    target without resolving it.
    cwd=<scratch> … > probe.txt was blocked while
    the identical > <scratch>/probe.txt was allowed — same destination, opposite
    verdicts, under a message naming a rule it had not applied.
  • A relative target is now placed against the directory the command runs in. That
    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.
  • The directory is the resolved one, not the cwd argument. A jail-rejected
    cwd is silently replaced with the project root, so judging the raw argument
    would let a caller name an out-of-project scratch dir, have the guard approve
    > probe.txt against it, and then have the command run in the project root and
    write there. ctx_shell resolves the run directory once and feeds the same
    value to the guard and to the run. Without a session the directory is unknown
    and the guard keeps its stricter earlier refusal.
  • Only the caller-supplied cwd is followed; an in-command cd is not. Deciding
    the 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_shell rejected ordinary read-only verification commands that used a
    bash conditional
    , e.g. git status --short; if [[ -n x ]]; then … →
    '[[' is not in the shell allowlist.
  • Two independent defects, both needed for the reported shapes. [[ was missing
    from SHELL_BUILTINS, although its POSIX equivalents test and [ were
    already members and it is bash conditional syntax evaluated by the shell
    itself. And split_on_operators shielded ( … ) and { … } but had no notion
    of [[ … ]], so the && inside a condition split the conditional into
    fragments that resolve to no real command.
  • Both delimiters must be standalone words to take effect, mirroring bash.
    Without that, a glob character class (ls a[[:alpha:]]) or an array subscript
    would 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.
  • The shield is scoped to operator splitting only. It does not widen what may
    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)
  • The shell export contradicted the Codex config. install_shell_exports
    wrote OPENAI_BASE_URL=…/v1 into every shell rc unconditionally, while
    install_codex_env deliberately writes nothing for a ChatGPT-subscription
    login — that rail answers a subscription token with
    401 … Missing scopes: api.responses.write, a message about organization roles
    that names nothing real. The environment overrode the config decision, so the
    careful answer never took effect and the 401 was what users saw.
  • Every other provider in that block was already gated on auth mode; OpenAI was
    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.
  • The strip deleted a setting lean-ctx never wrote.
    is_codex_proxy_model_provider_entry counted model_provider = "openai" as
    one of ours. lean-ctx writes leanctx-chatgpt, never openai, so that branch
    could 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).
  • #​1774 had closed the same report by appending an explanation to the 401 body.
    Routing the subscription token to the rail it actually authenticates on
    replaces that explanation with a working path.
  • The new shell tests pin CODEX_HOME, because the OpenAI line now depends on
    Codex's auth state, which resolve_codex_dir otherwise reads from the real
    ~/.codex — a test that tracked the developer's own login would assert
    nothing.
Fixed — the steering profiles say which tools the ctx_* mapping governs (#​1788)
  • The profiles told the agent "NEVER use built-in Read/Grep/Shell/Glob" and,
    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

❗ Important

✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 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.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot added the 📦 dependencies Pull requests that update a dependency file label Sep 24, 2026
@github-actions github-actions Bot added this to the v10.0.7 milestone Sep 24, 2026
@codacy-production

codacy-production Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

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
renovate Bot force-pushed the renovate/github-yvgude-lean-ctx-3.x branch 2 times, most recently from b2cf3b2 to 9bfe341 Compare September 25, 2026 23:26
@renovate renovate Bot changed the title Update dependency github:yvgude/lean-ctx to v3.10.2 Update dependency github:yvgude/lean-ctx to v3.10.3 Sep 25, 2026
@renovate
renovate Bot force-pushed the renovate/github-yvgude-lean-ctx-3.x branch from 7baadbd to 35c0c6d Compare September 26, 2026 13:44
@renovate renovate Bot changed the title Update dependency github:yvgude/lean-ctx to v3.10.3 Update dependency github:yvgude/lean-ctx to v3.10.4 Sep 26, 2026
@renovate
renovate Bot force-pushed the renovate/github-yvgude-lean-ctx-3.x branch from 9ae7008 to 5eb3b5d Compare September 27, 2026 21:30
@renovate renovate Bot changed the title Update dependency github:yvgude/lean-ctx to v3.10.4 Update dependency github:yvgude/lean-ctx to v3.10.5 Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📦 dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants