Skip to content

levain 0.7.1

Choose a tag to compare

@phillipclapham phillipclapham released this 10 Oct 16:13
· 4007 commits to main since this release

Closes two of 0.7.0's known open issues (the Linux resolver deputy and concurrent codex installs) and fixes defects in the codex adapter and ws-adopt.

Fixed

  • On Linux, bash with no IP network can no longer have the host's resolver, gnupg's dirmngr, snapd or tailscaled look up or fetch a name for it. Bash run as your own user (no levain setup-isolation) still saw the system D-Bus socket, and through it systemd-resolved resolved whatever name bash asked for, on the network; your gnupg dirmngr did the same for a key lookup. Wherever bash has no IP network (the default), the system D-Bus, systemd-resolved's varlink, nscd, avahi and tailscaled directories are now hidden and the snapd and dirmngr sockets masked, as bash run as the entity's user already had them. Measured 2026-10-10 on an Ubuntu host with a packet capture on port 53: a fresh name asked through resolvectl, varlinkctl, busctl and dirmngr's WKD_GET reached the wire before this change and does not with it. (tailscaled answers such a query only where the host made your user its operator; that case is closed by the same hiding, not measured.) Every socket named S.dirmngr that the kernel lists as bound in levain's network namespace when bash starts is masked, wherever its GNUPGHOME is (also one bash later points gpg at), besides the default gnupg places; if that list (/proc/net/unix) cannot be read or parsed, bash without network is refused. These masks are by path, and bash runs as you, so a bash that deliberately renames one of your own directories holding a running dirmngr can reach it from its next session and have it fetch (reproduced); the resolver daemons above live in root's directories and are not affected. That closes before Levain 2.0. Cost, in that bash: no systemctl or busctl on the system bus, no resolvectl, snap or tailscale, no gpg network commands (gpg signing through gpg-agent still works). A daemon whose directory did not exist when the shell started is not hidden, and neither are the snapd and dirmngr sockets once their daemon recreates them during the session (snapd restarting itself, say). Other daemons that fetch for any local caller over a unix socket (an LDAP-backed sssd or nslcd, a print server, a dirmngr under another socket name or running in another network namespace, such as a container, with its socket on a path bash can see) are not hidden: bash run as the entity's own user (levain setup-isolation) is the mode that has none of the host's sockets in view.
  • Two levain init --adapter codex (or levain update) runs from different installs can no longer mix their writes to codex's shared configuration. A lock on the codex home (a new file, <CODEX_HOME>/levain.lock) is now held across reading and writing both config.toml and hooks.json. A second init is refused before it writes anything and says so; a levain update that finds the lock held leaves the adapter files for review (exit 1) and changes nothing of them. Re-run either once the first has finished. Where the codex home cannot be locked (flock answers "not supported", as on some SMB or NFS homes, or on Windows), init and update refuse to write codex's files and say why; set LEVAIN_CODEX_HOME_UNLOCKED=1 to write them anyway when no other levain install writes that codex home at the same time. Reproduced on 0.7.0 through the CLI on macOS and Ubuntu: 5 of 60 concurrent pairs left config.toml naming one install's store and hooks.json the other's hooks, both runs exiting 0; none of 40 with this release.
  • If init cannot write codex's hooks.json, it puts config.toml back as it was. Before, config.toml was left registering the new install's memory server with no hooks behind it (reproduced on 0.7.0). The original is kept under a second name before anything is written and renamed back, so the undo needs no free space and keeps its mode (and, where the filesystem has hard links, its owner); a symlinked config.toml stays a link. If config.toml was edited after levain wrote it, the edit is kept and the original's name is printed. A crash in that moment, codex reading config.toml between the two writes, or an edit saved in the instant between levain's check and its put-back, is not covered.
  • init no longer crashes when codex's hooks.json is a symlink to a file that does not exist. It raised FileNotFoundError while taking the backup, after config.toml was already written. The broken link is now kept as the backup and a new hooks.json written.
  • A staged copy under .levain/pending/ is removed once its file is settled. After you adopted a staged copy (or levain wrote the file), the copy stayed, and adopting it again later replaced the file with an older release's version, which the next levain update then left alone. update now removes the copy and says so.
  • ws-adopt fails closed when git cannot read the source repository's config in full.

Known open issues

  • On Linux, bash without network can still reach your own gnupg dirmngr if it deliberately renames one of your directories. Bash runs as you, so it can rename the directory holding a running dirmngr's socket in one session and reach that socket from its next; dirmngr then fetches for it. The root-owned resolver daemons are not affected. This closes before Levain 2.0.
  • On Linux, a placeholder can outlive a levain that was killed. Unchanged from 0.7.0.
  • If levain cannot save its record of those placeholders as a session ends, the record can name files already removed. Unchanged from 0.7.0.