Skip to content

levain 0.11.3

Choose a tag to compare

@phillipclapham phillipclapham released this 17 Oct 04:14
· 3278 commits to main since this release

Added

  • levain messaging set isolate|governed|off|unmanaged changes an install's messaging choice without re-running init; levain messaging (or messaging show) reads it. set holds the install lock for the whole run, checks the choice first (claude-code adapter only, a readable .levain/config.json; nothing is written on a refusal), runs levain update for the install with the choice passed explicitly (the same renderer init --messaging uses, so the settings file is the same bytes a fresh init --messaging of that choice gives, loosening included; a plain levain update still never loosens), and records the choice in .levain/config.json only when .claude/settings.json carries exactly that choice's controls and the install's hook pin covers that file (also when the update stopped on an error after writing it, or held another file for review). Otherwise nothing is recorded and set says why; nothing is rolled back, and the next plain levain update re-renders the recorded choice (it keeps a tighter file and re-tightens a looser one). If .claude/settings.json was edited by you, update stages its version under .levain/pending/; set then says the choice is not applied and not recorded: merge the staged copy, then run set again. A successful change writes messaging_changed (from, to, at) beside the record, levain doctor's messaging row ends with "(changed from X )", and set prints LOOSENED X -> Y when the new choice is looser (strictest first: off, governed, isolate, unmanaged), or REPLACED custom messaging controls -> Y (stamped from: custom) when the file's controls matched no choice. show prints the effective choice (recorded; or, with nothing recorded, the choice the file's controls exactly match, as <choice> (inferred from .claude/settings.json; no record); custom (no record: ...) when it carries messaging controls that match no choice; else isolate, the default) and whether the workspace's settings carry its controls; it reads only the workspace settings file (doctor reads every layer) and writes nothing. --path defaults to the current directory.
  • Known edge, messaging set: an agent with shell access can run levain messaging set unmanaged as it can levain init --force --messaging unmanaged; levain records and shows the change (the stamp, the doctor row) and does not stop it. A workspace carrying an extra hand-added messaging control (a deny on another tool, say, beside the choice's own controls) reads as not carrying the choice, because the comparison is exact: set then records nothing and show reads it as custom.

Changed

  • The phone consent page (levain consent phone) now matches the cockpit's look (palette, type, card shapes, buttons; light and dark by the phone's setting); nothing it shows or signs changed. Known edges, phone page look: the waiting card's accent top line stays on a card already answered until you refresh (the page's script draws it, outside a look-only change); in light mode allow and deny differ mainly by hue, so the words "allow" and "deny" are always spelled out.

Fixed

  • Security: a confined levain run entity could read and rewrite the consent queue, and forge the cockpit authority it is checked against. The confinement floor hid <levain home>/autonomic and <levain home>/keys by name; the consent queue directory (~/.levain/consent, or under $LEVAIN_HOME), added later, was not among them, and neither was the cockpit data root its settings name (<install>/.levain/cockpit, which holds the authority log and its anchor). An entity's shell could read every Claude Code and Codex consent request, rewrite claude_code.json or codex.json, or replace that cockpit's authority log and anchor together with its own key, after which the consent hook allowed requests by the entity's grants and answers (measured). The floor now denies the whole levain home, read and write, as one subtree, so a directory levain adds there later is covered from the start, and it denies every cockpit data root the consent settings name. Upgrade if you run entities with levain run on a machine whose Claude Code or Codex installs use the consent hook.
  • Known edges, the floor and the levain home: (1) when the levain home holds the entity itself (an install that lives at ~/.levain and is run as an entity) or its workspace, or $LEVAIN_HOME is your home directory, the home cannot be hidden whole without hiding the entity, so the floor hides its autonomic, keys and consent directories by name instead; a directory a later release adds there is covered in that layout only once that release names it. (2) The cockpit roots are read when the floor is built: one a levain init names while an entity's session is running is denied from that entity's next session. (3) A queue or key directory you place outside the levain home (--queue-dir, levain cockpit key create --dir) is not on the floor; the defaults are. (4) A Claude Code or Codex session itself is not confined by levain and runs as you, so it can write these files whatever the floor says, as before. (5) A relative $LEVAIN_HOME is taken against the directory levain run starts in, while a hook takes it against its own; set it to an absolute path. (6) The floor refuses to start while a consent settings file is not a regular file levain could have written, or names its cockpit through a symbolic link; levain init writes the real path. (7) On Linux, a consent settings file naming a cockpit directory that no longer exists (its install was deleted) has the floor create an empty placeholder for it while a session runs, or refuse to start when its parent cannot be written; remove the settings file, or run levain init from the install that should answer.
  • The Codex adapter's README and hook docstring said Stop is excluded; Codex's Stop event is wired (it passes the session transcript), and both now say so.