Skip to content

io-cli 0.15.0 - #42

Merged
aakashpawar1999 merged 11 commits into
mainfrom
develop
Aug 25, 2026
Merged

io-cli 0.15.0#42
aakashpawar1999 merged 11 commits into
mainfrom
develop

Conversation

@aakashpawar1999

Copy link
Copy Markdown
Contributor

Release PR for io-cli 0.15.0 — one home, ~/.io-cli.

Everything io-cli keeps now lives in one directory with one name on every
platform: the configuration file, the run store, the durable memory that is rows
inside that store, and a skills directory created with the home. An existing
install is moved into it on the first run, with each file that moved named on
screen, nothing overwritten and nothing deleted. An operator who has set
IO_CONFIG or IO_CONFIG_HOME is not moved at all.

Merged from feat/0.15.0 in #41 with 8/8 checks green on three platforms.

The Windows row earned its place this round. Its first run failed on a real
defect that no macOS or Linux run could see: Windows refuses to rename a file
another process holds open, and adopt was ignoring the failed move — so a second
io running would have let io.toml reach the new home while runs.db stayed in
the old one, which is the config-and-store-in-two-places split this whole release
exists to prevent. It now moves everything or nothing, takes back whatever already
moved, and names the file that was in the way.

The UltraShip record is sealed and pinned
(sha256:51848093d751c7ccee5cd96977453c069a1386d1865127a22a06b7255d9bdcc4,
validate exit 0 over 45 checks); .ultraship/ is gitignored here, so it lives on
disk and never in the history. The Windows fix landed after the seal and the seal
was not amended — the record's own known_limitations already says the Windows
arm runs on CI and nowhere else, which is exactly how it was found.

Suite: 57 binaries, 719 passed, 0 failed, 24 ignored (0.14.0 finished at 696).

… into it

io-harness resolves $IO_CONFIG, then $IO_CONFIG_HOME, then the platform's own
place, reading the environment at call time — and exposes no caller-supplied
home. So the home is named by setting that variable once, before the first
Config::discover, and the run store follows because settings::store_path derives
it from the configuration file's own directory.

The migration moves io.toml and the store together, and the store's -wal and
-shm with it: a runs.db moved without its write-ahead log opens without
complaint and simply does not contain the last session. Nothing is moved onto a
file that exists, and a source is removed only after its copy has been read back.

An operator who set either variable is not moved at all. A variable pointing at
io-cli's own home reads as 'default' rather than as their choice, so a status row
cannot credit them for this crate's default — which is what an origin read from
the raw environment did, because adopt is what put the variable there.
home::adopt runs immediately before Config::discover, the only discovery
either arm reaches, because user_path reads the environment at call time: a
config discovered first would come from the old location while the store
answered from the new one, which is a silently emptied /resume.

The report is committed through App::record rather than App::say — it belongs
to the conversation, not to the keystroke that happened to be next — and takes
its own Tone::Muted vec rather than joining the notices, which are Warning and
would paint a permanent yellow line at every launch. In the headless arm the
same lines go to stderr before the handoff, never to stdout.

tests/structure.rs pins the ordering by reading src/main.rs with every comment
stripped first, so a comment naming adopt cannot satisfy the gate.
Spawns the built binary, because the printing lives in main.rs and no
integration test links it. `exec --policy ask-writes` is refused on exec::main's
first line, before a store, a session or a provider — so the run terminates
with no credential and no network while still executing everything that runs
before the branch, which is where the report is written.
…s a home

Skills::discover does not walk away from a directory that is not there — it
returns Err, and discover_skills propagates that at run start, before the first
completion. So an unconditional default would have failed every turn for every
operator who had never made the directory. adopt creates <home>/skills with the
home, and the default is filtered on is_dir() besides.

io-harness substitutes ${env:} and ${file:} and nothing else, so a ~ written in
[run] skills or [app.io-cli] skills reached Skills::discover as a directory one
character long. The expansion happens once, after both keys have had their say.
attach.rs's own twelve lines of it now call the same helper.

Precedence, weakest to strongest: <home>/skills, [run] skills, [app.io-cli] skills.
…ded it

Derived from home::in_force and never from home::path: under IO_CONFIG the file
is somewhere io-cli did not choose, and reporting the home this crate would have
picked would be wrong in the one case the row exists for.

tests/plain.rs asserts every row of the plain page is ASCII, and the new row
carries a directory read from the environment — an assertion about whoever runs
the suite. Pinned to a literal, not to a temporary directory, because a
temporary directory is $TMPDIR and that is the same ambient answer one step on.
…nnot work

The resolution order is io-harness's and unchanged; what changed is that io-cli
answers its second rung by default. The migration and its opt-out are stated up
front, along with the fact that the variable is set in io-cli's own process and
inherited by every child a session starts.

The skills example named ~/.config/io/skills, which was wrong twice: the
location moved, and io-harness expands no tilde at all.

The README's discovery prose had no gate, which is how it could go stale. The
new one derives the directory name from home::path and walks the four rungs by
byte position, so a README naming all four in the wrong order fails.
The version and the notes. The migration is the first thing the notes say,
because it moves files io-cli did not create on the first run of the new
binary, and rolling back is a variable or a move rather than a loss.
Found by running the binary, twice. With a file io-harness cannot parse the run
ends at the discovery; with an unreadable store it ends at Store::open. Both are
before either arm reaches the place the report was written, so an operator saw an
error naming a path they had never seen, one keystroke after their old directory
emptied, with nothing anywhere saying their install had just moved. That is the
'my sessions are gone' reading this release exists to prevent.

The report is now owed until somebody delivers it: run fills a vec main owns, io
exec drains it onto stderr, a session takes it into the scrollback, and main says
whatever is left. Patching each early return would have left the next one out.

The test spawns the binary, because the ordering lives in main.rs and nothing
under tests/ links it. Its sabotage — report only after a successful discovery —
fails it and nothing else.
…oing it

Windows CI found this and this machine could not. Windows refuses to rename a
file another process holds open, so a second io running while this one starts
would let io.toml reach the new home while runs.db stayed in the old one — a
configuration and a store in two directories, and a /resume that finds nothing.
That partial state is worse than not migrating at all, and adopt was ignoring the
failed move.

Now the variable is set only once every file that had to move has moved. A file
that will not move takes everything already moved back with it, leaves the
environment alone, and is named on screen with the directory still in force.

f4's fixture was the shape that hid it: it held the store open across the move,
which is legal on unix and impossible on Windows. It now builds the store in a
staging directory, copies it while the write-ahead log is live, and drops the
connection before anything moves — which is byte for byte what a crashed io
leaves, on either platform, with nothing holding it.
io-cli 0.15.0 — one home, `~/.io-cli`
@aakashpawar1999
aakashpawar1999 merged commit dac2cc7 into main Aug 25, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant