0.2.1
Stops and Sandboxes
A minor update with miscellaneous clean-up work during the week after the 0.2.0 release. A first external review provided many good pointers and bugs were found by intense dogfooding. This update also includes work on the sandboxing strategy.
334 commits by Gantry runs
89% of the release
40 commits by non-Gantry sessions
11% of the release
Gantry 0.2.1 covers the six days from the 0.2.0 release to 2026-08-10: 374 commits in 23 units of work — 9 gantry runs contributing 334 commits, and 14 hand or interactive-agent sessions and standalone commits contributing the other 40. It is a last-digit release — new capability and fixes, safe to upgrade — with two exceptions named below.
The largest single gain for someone holding a downloaded binary is that a run can now be played
back and repaired in the build they were sold. The screencast build feature had been gating
two unrelated things: the filming pipeline, which nobody outside the project has, and the
durable event tape with replay, repair, repairs, backfill-journal and
backfill-journals. A run moved the filming code out of the crate entirely and left the record
half unconditional, so every build now writes an event tape and can replay it; gantry list and
gantry status also gained --json.
Stops and recovery were rebuilt across two runs after an incident in which a run parked for
eight hours on a tree its own troubleshooter had already fixed and then discarded the operator's
uncommitted work when they answered Proceed. The engine run split run start so the
interrupted-worktree reset can only fire when taking a worktree over from a dead owner, parked
any tree it does discard on a ref under refs/gantry/discarded/, made every interpreter step
record a terminal journal entry by construction, and collapsed the recoverability guess into one
stop state with one automatic recovery attempt. The front-end run then deleted the TUI's own
phase state machine — the reason a healthy run could render as a frozen stopped screen — and
derived the phase from the engine's projection instead.
The sandbox went out and came back. One run rebuilt the bubblewrap profile as a write boundary —
read-only host root, a per-run private home, private temporary storage, credential exemptions —
it broke agent launches the day it merged, and a second run removed it in full and settled the
posture instead: Gantry ships the isolation it needs for its own correctness (the disposable
worktree, the process fence, the main checkout bound read-only) and leaves confinement to a
boundary the user draws around Gantry. Two configurations that worked in 0.2.0 stop working.
GANTRY_SANDBOX=bwrap is no longer an accepted spelling; it now falls through to the
best-effort default, so a host without bubblewrap that used to refuse to start will warn and run
without a process fence — use 1, on, true, require or required to require it. And the
sandbox.confinement_check config key with its GANTRY_CONFINEMENT_CHECK variable are gone:
the check is now always on.
One default changed that is not a removal but will be felt. The bundled map, careful and
steady patterns dropped their declared pool_width from 4 to 1, so a map run puts rows
through the pipeline one at a time and concurrency is asked for with --jobs. In the same area,
the comments inside every bundled pattern were rewritten; no engine-read line changed, but a run
folds its frozen pattern source into its content identity, so a fresh run of an unchanged plan
now resolves to a different run id than 0.2.0 would have produced. Runs already on disk keep
their own frozen copy and resume unaffected.
An independent reviewer read the 0.2.0 source-review archive on 2026-08-04, and two runs
executed the unambiguous half of their findings: durable ledger and journal writes no longer
fail silently, gate commands are bounded by GANTRY_GATE_TIMEOUT_SECS at four hours, top-level
argument handling gained -- and stopped mistaking -r for a resume before verb detection, and
clippy joined the gate. A hand session fixed a prompt defect in the same spirit: free text sent
to a two-choice blocker prompt had been read as approval. Elsewhere, blocker investigation agents
can now open in their own terminal window, the monitor stopped re-reading each unit's pattern
closure from disk on every frame, and Gantry stopped committing its own run status guide into
the user's project. The demo edition's unlock key was rotated after it was found to have been
reset to a public test key, and the key was moved out of the directory that is compiled into
every binary.
Stops and recovery
Two runs rebuilt stop handling after a run parked for eight hours and then discarded uncommitted work.
-
44 commits,
d31d83d867c186e695runstop-recovery-enginereplayOne stop state, the gate verifies, and no work is destroyed
A gantry run rebuilt the engine's stop handling after an incident in which a run parked for eight hours on a tree its own troubleshooter had already fixed, then discarded the operator's uncommitted work when they answered Proceed. Run start is now split into a once-per-process ownership acquisition step and the re-entrant run, so the interrupted-worktree reset only fires when the engine takes a worktree over from a dead owner and can never fire on a Proceed; any tree it does discard is first parked on a ref under `refs/gantry/discarded/`, named on the support card, and reclaimed by age through `gantry worktrees` and `prune`. Every exit from an interpreter step now records a terminal journal entry by construction rather than leaving the step open, which is how the failed halt hook left no trace, and a halt hook whose gate is green now counts as resolved even when its agent wrote no disposition file, with an explicit handover still winning. The recoverability guess on stop records is gone: there is one stop state, every stop gets the single automatic recovery attempt, operator pause is recorded durably in the journal instead of being dropped, and headless exits 0 when the run reached an intended end (completed, no work, operator pause) and non-zero for any other stop. The second confirmation modal on Proceed was deleted, and the mechanical support stage was renamed so it no longer reads as a problem-solver that gave up. Two commits were fixed by hand mid-run to keep the recovery policy intact while the guess was being removed.
-
19 commits,
805d9a61ed4e57b4b2runstop-recovery-front-endreplayThe TUI reads run state from the engine's projection
A gantry run built the display half of the same incident, in one milestone of six sprints, all green. The TUI had kept its own run-phase state machine that entered a stopped state on a journaled stop and left it in only one place, so a healthy run — agents working, gates passing — rendered as a frozen stopped screen that reproduced identically on every cold start; the phase is now part of the engine's run projection and derived from the same folded facts headless already used, so no code path can set a stale phase. The run controls, previously declared four times over in menu reasons, enable predicates, buttons and dispatch guards, are now one action table, which ends menu entries that contradicted each other; a stopped run can also reach proceed, stop, shell and investigation actions without a live prompt. The TUI now seeds its view from durable run state on a cold start and falls back to folded counts for the progress label. Alongside that, the ledger stopped re-recording an already-completed sprint row on replay, process-roster replay learned to read legacy nested rosters so `gantry status` no longer reports zero sprints and zero live process groups on a real run, and durable roster writes were separated from the originating orchestration directory.
The agent wrapper and the process fence
A write-boundary rework was built, broke agent launches, and was removed again; the posture that replaced it is what ships.
-
49 commits,
d006aa98c30894c763runsandbox-private-homereplaySandbox write-boundary rework, undone later in the same release
A gantry run of three milestones rebuilt the bubblewrap profile as a write boundary: a read-only host root with explicit writable binds, a per-run private home mounted over the operator's, a private `/tmp`, per-harness credential exemptions, operator exemption lists, and the mode spellings and environment variables that went with them (`hostrw`, `GANTRY_SANDBOX_RW`, `GANTRY_SANDBOX_RO`, `GANTRY_SANDBOX_STRICT_CREDENTIAL_CHECK`). It broke agent launches on the day it merged and was removed in full by the sandbox-back-to-process-fence run later in this same release, so none of that boundary, and none of those spellings, reaches a customer — a reader upgrading from 0.2.0 sees no write boundary that was not there before. Two pieces of the run survive the reversal and are what it actually delivers: one shared profile builder that both the engine's agent spawn path and the MCP `run_bash` tool use to construct the wrapper argv, replacing two forked copies, and a journaled sandbox-visibility event that records whether the process fence is actually active rather than degrading silently.
-
3b975a376commitThe run's private home stops covering the real home directory
A hand fix on the day the write-boundary rework merged and broke every run. Mounting a run-owned directory over the operator's `$HOME` hid the harness binaries installed under it — `~/.local/bin/claude`, `~/.opencode/bin/opencode`, an nvm-installed `codex` — so bubblewrap could not exec the agent at all and runs died on their first agent step, each harness reporting the failure as its own empty answer file; `gantry doctor` stayed green because its smoke test builds the profile without a run's writable areas. The fix stopped the run's writable areas requesting a home mount. It repaired an intermediate state that the sandbox-back-to-process-fence run then removed outright, so no released behaviour depends on it.
-
26 commits,
457d96033e79d39464runsandbox-back-to-process-fencereplaySandbox returns to the worktree and the process fence
A gantry run of two milestones reverted the write-boundary rework and settled Gantry's isolation posture: it ships the isolation it needs for its own correctness — the disposable worktree, the bubblewrap process fence, and the main checkout bound read-only with its `.git` carved back writable — and leaves confinement to a boundary the user draws around Gantry, which the documentation now describes instead of overselling a bind list. The read-only root, the per-run private home, the private temporary storage, the per-harness credential binds, the operator exemption lists and roughly a thousand lines of profile machinery were deleted with their tests; the sandbox vocabulary throughout the binary, the shipped manual and the agent prompts was rewritten from confinement to wrapper state, and `run_bash`'s tool description and per-result metadata now report only whether the process fence, a custom wrapper, or nothing is in effect. Two changes break configurations that worked in 0.2.0. `GANTRY_SANDBOX=bwrap` is no longer an accepted spelling: in 0.2.0 it required the bubblewrap wrapper, and it is now unrecognised and falls through to the best-effort default, so a host without bubblewrap that used to refuse to run will now warn and run without a process fence — `1`, `on`, `true`, `require` and `required` still select the required bubblewrap default, and `0`, `off`, `false`, `no` and `none` still disable it. And the `sandbox.confinement_check` config key with its `GANTRY_CONFINEMENT_CHECK` environment variable are gone; the post-stage check that unsandboxed agents did not write into the main checkout is now always on and cannot be switched off. `GANTRY_SANDBOX_CMD` is now the documented extension point for users who bring their own wrapper.
What ships in a build, and the external audit
An independent review of the 0.2.0 source archive drove two runs; a third moved the filming code out of the crate and left the record-keeping half in every build.
-
39 commits,
bb01a80f68e1d93fc8runexternal-review-unambiguous-fixesreplayFixes from the external audit of the 0.2.0 source archive
An independent reviewer read the 0.2.0 source-review archive statically on 2026-08-04; a gantry run of three milestones executed the subset of their findings that was unambiguously wrong, leaving matters of policy and threat-model positioning alone. Durable writes no longer fail silently: a failed ledger append is now a run-level error instead of a discarded result — previously a DONE row that failed to write meant the sprint silently re-ran on the next resume — and a failed journal append is reported once rather than dropped without trace. Top-level argument handling gained the standard `--` end-of-options escape, stopped normalizing `-r` / `--last` / `--continue` before verb detection (so `gantry logs -r <name>` no longer resumes a run called `logs`), and made `--last` choose from the same resumable candidates the interactive picker offers. Gate commands became time-supervised: `GANTRY_GATE_TIMEOUT_SECS` / `[gate].timeout_secs` bounds every gate at four hours by default, and on expiry Gantry kills the gate process group, notes the timeout in the gate log, records the stage as timed out, and follows the ordinary red-gate path. The assistant's MCP socket moved under the protected per-user runtime directory, live statistics are now tracked per stage instance, process and TUI text wraps by display width instead of byte count, `clippy` joined the root gate, and the release packer now ships `clippy.toml`, redacts references to deliberately withheld documentation from the README files it stages, and refuses to build an archive whose markdown points at files the archive does not contain.
-
20 commits,
84031ed4dccfd59803runstructural-liabilities-cleanupreplayStructural cleanups, and corpus reconciliation limited to devtools
A gantry run worked through six independent cleanups drawn from the same external review, under a fixed external contract: observable behaviour was to stay the same. Worktree-hosted candidate detection was centralized across the fleet-record modules, the artifact reclamation engine behind `prune`, `purge`, `worktrees` and `build-directories` moved out of `src/commands.rs` into its own module, stop-card rendering was pulled out of the domain layer into `engine/stop_render.rs`, the diagram module stopped splicing its parts together with `include!` and became real submodules, and a scattering of hand-rolled key-value parsers were routed through the existing `tiny_config` reader. The one deliberate exception to the no-behaviour-change rule: `gantry stats --reconcile-corpus` and `--reconcile-corpus-path` are internal fleet-corpus tooling and are now compiled only into devtools builds, so they are absent from a distributed binary.
-
104 commits,
ba490e3a73186b76b0runscreencast-extractionReplay and record repair in every build; filming code left the crate
A gantry run finished all five of its milestones green: it deleted the `screencast` Cargo feature, moved the filming and narration code out of the `gantry` crate into a separate workspace member that drives the binary over its command line, and rewrote `Cargo.toml`, `src/main.rs`, `src/commands.rs`, the verb and flag specification and the embedded manual to match. Almost none of the removed surface was ever something a customer could see — release builds compile with `--no-default-features`, and the 0.2.0 stripped-build help text listed no `screencast`, `tts-narrate`, `check-narration`, `compile-menu-tour`, `menu-tour-model` or `validate-menu-tour` verb and none of the timelapse or narration flags. What the release does change is the useful half that the same feature had been gating: `replay`, `repair`, `repairs`, `backfill-journal` and `backfill-journals` are now in every build, together with `--speed`, `--timing`, `--target-length`, `--adaptive` and `--hold-final`, and so is the durable event tape itself — a stripped build previously never wrote one, so a customer's own runs could not be replayed at all. `gantry list` and `gantry status` gained `--json`, emitting the repository's runs as newline-delimited run records, and the compiled-in manual page on commands gained a paragraph covering replay and the four record-maintenance verbs. The source tree a source-review customer receives is materially smaller: `src/` lost roughly 4,400 lines against about 1,000 added, with `engine/menu_tour.rs` (965 lines) and `engine/tts.rs` (866) deleted outright and some 1,240 lines of command implementation and 640 lines of verb and flag specification going with them. The new filming crate is a workspace member, so the archive carries its crate root and its 1,400 lines of source — the shipped workspace manifest has to resolve — but nothing that drives it and no part of it reaches the binary.
-
5 commits,
a4979e506363f33746sessionApproval-prompt fix, dead-code audit, and a map disjointness rule
A Claude Code session working by hand fixed a prompt-handling defect and then audited two code-standards problems across the tree. The fix: a blocker prompt offers `proceed` or `stop-run`, but any non-empty free-text answer was mapped to `proceed`, so prose sent to a choice prompt — including prose arguing to stop — silently approved the run continuing; now the engine prompter drops an ill-formed answer and keeps waiting, blocker resolution falls back to the safe stop default, and the MCP `answer_prompt` bridge rejects a mismatched answer immediately and names the valid choices instead of leaving the caller to wait out a timeout. The audit removed all thirty blanket `#![allow(dead_code)]` suppressions and, with them, about 500 lines of genuinely dead code — a mutation-injection apparatus whose tests died in the July test purge, duplicate config accessors, never-wired config writers, a parallel MCP run-resolution layer, and orphaned test helpers — scoping what survived to `#[cfg(test)]` or item-level allows with stated reasons; it also restored source literals that had been split with `concat!` purely to hide words from a naming checker. Separately, the bundled map-spec planner prompt now requires the worker preamble to tell each unit to write only its own artifact, because map units run concurrently in one shared worktree with no write isolation and disjointness rests entirely on the planner.
The monitor and run records
-
15 commits,
ccc3d32d77a63aaa83runexternal-agent-new-windowreplayBlocker investigation agents can open in their own terminal window
A gantry run built this in one milestone of four sprints, on the Codex harness, with no stops. Until now, answering a blocker prompt with "launch a harness" or "open a shell" ran that agent through the TUI: the monitor left the alternate screen and the investigation agent took over the terminal until it exited. An operator can now keep both on screen — the new `ui.external_agent` setting (`GANTRY_EXTERNAL_AGENT`, `auto`/`window`/`inline`, default `auto`) decides whether a blocker-launched agent may open outside the monitor's terminal, and `ui.terminal_command` (`GANTRY_TERMINAL_COMMAND`) names an argv prefix to launch it with, written as a TOML array in the config file or whitespace-split in the environment. With no configured command Gantry consults only published launch interfaces in order — `tmux new-window` under `$TMUX`, `screen -X screen` under `$STY`, `$TERMINAL -e`, `$TERMCMD -e`, `xdg-terminal-exec`, `x-terminal-emulator -e`, with the graphical tiers requiring `$DISPLAY` or `$WAYLAND_DISPLAY` — and never inspects `$TERM`, walks process ancestry, or carries per-emulator tables; when nothing resolves it suspends in place as before and prints a hint naming `GANTRY_TERMINAL_COMMAND`, while `window` refuses to fall back silently and says why. Because a detached investigation may still be running, answering that same blocker with Proceed or Stop now asks for an explicit confirmation first, while launching another investigation or opening the worktree shell stays immediate. A new `gantry external-agent [harness-id]` prints the resolver's decision and the exact command it would run without opening a blocker or spawning anything.
-
3 commits,
42d7f22ebd17b4ad67sessionMerged runs report as finished, and run cards name the real work
Three fixes in one Claude Code session. `gantry list` had been reporting a fully built and merged run as active for as long as the monitor that ran it stayed open: the registry derived "the worktree exists" from the run lock, but finishing a run removes the worktree while the owning process holds the lock until the operator quits, so every merged run read as active and offered enter or resume on a directory that was gone; the worktree is now checked literally, and the lock consulted only for a run that has no recorded units at all. The naming pass that titles plan and milestone cards was fed one line of each brief and a prompt asking what the work "builds", so a milestone whose sprints only rewrote comments was titled "Implement the fleet record layer" — it now receives each task's whole brief plus the run-level card saying which larger job the sprints belong to, and the bundled `write-about.md` and `title-plan.md` prompts drop the build vocabulary, since sequencing agents to build is one pattern's job and not the engine's. Third, a replayed run no longer hands its whole screen to a `STOP — your turn` pane: the stop still folds into the feed as a notable event and the phase still reads Stopped, but the pane-filling entry is suppressed during replay.
-
7 commits,
efc54fc25770248128sessionExcluded paths kept out of commits, plus monitor and merge speed
Seven hand and interactive-agent fixes in one day. The most consequential is a correctness bug in what Gantry commits into the user's project: `GANTRY-STATUS.md`, the run status guide regenerated in a worktree whenever anyone opens a shell or harness there, was being committed onto the run branch and merged into the project, where later runs' agents read a finished run's status as their own. The exclusion meant to prevent it had never worked in any linked worktree — Gantry derived the exclude file's path instead of asking git for it, and wrote to a location git never reads — so it now asks (`rev-parse --git-path info/exclude`), which repairs the exclusion for every ephemeral file Gantry writes into a worktree: harness context files, `.mcp.json`, harness answer files, the runtime tape, seed files. A follow-up commit closed the class rather than the instance: Gantry's own commit now unstages any newly added path the checkout's exclude file names literally, keeps refusing the status guide by name, and leaves alone any path the project genuinely tracks. Second, the monitor's responsiveness: hovering or clicking took seconds because the activity feed and the Inspect menu re-read each unit's frozen pattern closure from disk on every frame, capping the input loop near 13 frames a second; both are memoized behind short lifetimes and the feed reads through a side-effect-free path, taking the draw closure from 80 ms to 1.2 ms on a 99-card replay. Third, the classifier that checks after a conflict resolution whether the resolution dropped anything main contributed re-ran the branch's entire diff once per hunk — about 100 minutes on one wide branch, with no sign of life on the Resolve card; it now reads the branch delta once into a per-file index, caches resolved files, excludes every run's orchestration tree from both diffs, and reports progress while it sweeps. The remainder: the run journal is closed before the worktree is removed, so a completed run no longer prints a missing-directory warning over the live monitor; the sidebar's position marker leaves a finished sprint once the next milestone starts planning; and Esc or the feed's Bottom control clears the selection cursor so the feed can return to its live tail.
-
2 commits,
3d3321ccaeffff882bsessionA resumed run keeps streaming, and review cards name their unit
Two hand fixes. Resuming a run replays its durable journal into the live front-ends so the feed and sidebar carry the earlier sessions' work, but that replay included the `Stopped` record the previous session had written, and every live consumer reads a stop as the run ending now — the attach pump wound down and stopped forwarding, leaving the monitor frozen on the old error with the phase reading Stopped while the engine kept working underneath. Live replay now skips stop records; cold readers such as `gantry status` and the run-state projection keep them, since they need the stop to say where a still-stopped run left off. Separately, a review card read "Reviewing" at both altitudes of the build pattern, so the feed gave no way to tell a sprint review from a milestone review; each now names its unit.
Patterns and prompts
-
18 commits,
b605cddd53397ee9a1runpattern-diagrams-vertical-flowPattern diagrams read downward and fit a width budget
A gantry run reworked the pattern diagram renderer that 0.2.0 shipped, which drew charts that ran sideways: `gated-task-set` at `--detail steps` was 1,515 columns wide and 131 lines tall, so the main flow could only be followed by scrolling horizontally. The run added a per-edge dominance hint to the generic diagram model, set by Gantry's pattern builder from pattern semantics, so ordinary continuation and the dominant side of a branch stay in the same column directly below their referrer while genuine divergence — recovery blocks, rule blocks, finalizers, peer case fan-out — still claims a side column. It gave the layout contract a target width, honoured by placing a divergent subgraph below rather than beside when a new column would exceed it, and exposed it as `--width <columns>` on `gantry pattern show` and `gantry pattern snapshots`, defaulting to the terminal width when attached and to a stable 120 columns otherwise, with `--width 0` for unbounded output; the terminal widget passes its viewport width. Gate conditions, which had been rendered with Rust's `Debug` formatting and could occupy 300 columns on their own, are now written in the pattern language's own vocabulary and wrapped to a maximum node width. The committed review snapshots were regenerated: the same `gated-task-set` view is now 51 columns wide and 8,164 lines long — readable by scrolling down, at the cost of considerable length. The run stopped once and was resumed, then its final chunk was interrupted twice when the owning process died and it ended without a completion record; the layout, labelling and snapshot work had already landed by then, and a hand merge took it across.
-
5 commits,
9ccd502773bb061673sessionOne-file reading copy of the default build pattern, later removed
A Claude Code session restated the three bundled files a flagless `gantry <plan>` executes — `build.toml`, `milestone.toml` and `sprint.toml` — as one 729-line pattern file and shipped it in the bundled set as `config/patterns/build-in-one-file.toml`. It declared `included_only`, so the registry listed and exported it but refused to run it: a reading copy of the composition, not a selectable pattern, and nothing about what a flagless run does changed. It did not survive the release — the hand-authored restatement was judged not worth repairing and was deleted the same day, so 0.2.1 ships the same ten bundled patterns as 0.2.0.
-
4 commits,
8117b4ebe8e94e3235sessionBundled pattern files re-commented in a terse literate style
A Claude Code session rewrote the comments in every bundled pattern — build, milestone, sprint, ralph, map, goal, careful, steady, design and redesign — cutting the long composition essays about include semantics and legacy executors, and giving blocks that several patterns share one wording. Every TOML line the engine reads is byte-identical, so no pattern's behaviour changes, but the comments are not inert: a run folds the raw source text of its frozen pattern closure into its content identity, so a fresh run of any of these patterns over an unchanged plan now resolves to a different run id and `.gantry/<id>/` directory than 0.2.0 would have produced. Runs already on disk keep their own frozen copy and resume unaffected. The same session deleted `config/patterns/build-in-one-file.toml`, the reading copy added earlier that day.
-
b7687f72ccommitMap runs process rows one at a time unless you pass --jobs
A changed default: the bundled `map`, `careful` and `steady` patterns dropped their declared `pool_width` from 4 to 1, so a map run now puts rows through the pipeline one at a time, and concurrency is something the operator asks for with `--jobs <N>`. Anyone who relied on the previous four-wide fan-out has to pass it explicitly. The same hand commit rewrote the three patterns' descriptions and parameter text, which had led with "fan-out" and "embarrassingly-parallel" and named the pool as the only thing worth knowing: they now describe what the pattern does — spec authoring with a bounded repair loop, classify, then per-row build, gate and repair, with checkpoint QA sampling finished rows and a failure-streak threshold that stops the run — and state that widening the pool is the operator's judgement, because Gantry does not check that rows are safe to run concurrently. The README's `--map` line and the map executor's module comment follow the same wording.
-
6d04f8f30commitAgents are told which plan files exist and which they may edit
A Claude Code session fixed the environment section every agent receives, which named two plan-shaped artifacts and stated the edit rule backwards on both. All three are now named by their real per-run paths: the source plan, described as the run's axiom with the rule that had lived only in a Rust error string (writing under it halts the run); the flattened plan copy the front-ends display, previously unmentioned, so an agent finding two plan-shaped paths no longer has to guess which is authoritative; and the orchestration directory's task briefs, no longer described with a `NN-slug.md` sprint path that a milestone run does not have. The blanket "don't edit anything under it" — wrong, since a reviewer is supposed to edit briefs — is replaced by the accurate rule: read the orchestration directory freely, write only a file your own task instructions name. The shipped review prompt had the matching gap, telling the reviewer four times to edit "the scoped plan files the active pattern permits" without naming them; it now interpolates the actual list, drawn from the step's `protected` declaration, with each entry marked when its edits will be withheld and handed to the declared handler as a draft.
The demo edition and the source archive
-
ffd830770commitDemo completion reports send a hash, not the plan name
One commit changed the demo edition's completion report to send a SHA-256 of the plan name in place of the name itself. The plan name is a slug of the user's own plan file and so says what they are working on; the licensing server only ever stored it hashed, and only needs a value that is stable across retries of one build and different between builds, which the hash satisfies. No server change was needed.
-
4 commits,
c406ec93ece10b2efbsessionDemo unlock key rotated and kept out of paid builds
A Claude Code session rotated the demo unlock key and added the checks that were missing. Nothing had ever verified `config/demo-unlock.pub`, which is how it came to be reset to the deterministic integration-test key the day after the real key first landed — both 0.1.0 and 0.2.0 therefore shipped a key whose private half is public, leaving the demo build cap bypassable and the paid unlock unverifiable. The release preflight now refuses to cut on either known test key, on a key still carrying the `ed25519-public-v1:` prefix the verifier does not strip, or on anything that is not a 43-character base64url Ed25519 key, pinning the known-bad values rather than the good one so a genuine rotation is never a fixture edit; the file's own documentation, which had described the committed value as a placeholder, was corrected. The key and that documentation also moved out of `config/`, which is embedded into the binary wholesale, into `config-demo/`, so a paid binary no longer carries the demo unlock key at all, and the demo sources are pruned by name from the source-review archive. The archive was checked end to end: no demo path in the zip, and the extracted sources compile as a paid build — the failure that broke the 0.2.0 cut.
Maintenance and the release cut
-
2 commits,
9d23fab0231f1cc3d8sessionmcurses widgets re-vendored at upstream f80a0e5
Hand work re-vendored the three mcurses crates compiled into the binary, reconciling in both directions rather than copying upstream over the tree. For a user the visible part is text measurement in the menu and select widgets, which now follows the same width policy as the rest of the display stack — unicode-width 0.2, zero-width characters taking no columns, no synthetic columns for tabs — plus a per-selection metadata cache in the clipboard crate; downstream-only mutex-poison handling was pushed upstream instead of being dropped. A second commit stopped vendoring upstream's own documentation and planning papers, which only ever went stale in this tree.
-
cd3374c94commitCodex agent runs turn off the Codex CLI's shell snapshot
Codex replays the operator's login shell into every command it runs; Gantry now passes `-c features.shell_snapshot=false` so an agent in a disposable worktree gets a plain shell, and hosts whose login shell cannot be re-sourced stop seeing a snapshot-validation error from every spawned agent. Codex versions without the key ignore it.
-
3 commits,
cdca8da9270222aa7csessionRelease 0.2.1 cut, and the three defects that blocked it
Hand work bumped the crate version and regenerated the reference dataset, then repaired the release builder, which refused to produce this release three times over. The filming crate had become a workspace member without joining the shipped source include list, so the stripped tree named a member that was not on disk and cargo would not load the manifest; its crate root and sources now ship, for the reason the list already gives for the comment-stripping tool. The redaction of withheld documentation from the agent-prompts README ran while staging the archive, but that file is compiled into the binary, so the producer built one set of bytes and a customer rebuilt another and their hashes could never agree — the redaction now runs on the stripped tree before any build, and both shipped targets pass the customer round trip for the first time. And the commit sha stamped into the binary was re-read from the repository once per build rather than pinned like the build timestamp beside it, so a commit landing mid-cut gave two builds of one tree two different stamps.