A strict correctness/security audit pass. Every fix ships with a regression test
proven to go red on the pre-fix code (except the two paths the suite tests
manually — the watch tail loop and --ephemeral stash race — verified by a
standalone harness instead).
Security
-
Ephemeral/burn lock files moved out of world-writable
/tmpinto the private
$HOME/.clikae/state(0700). Their names are predictable
(ck-ephem-<run_id>/ck-ephem-slot-<cksum>), so in/tmpanother local user
could plant one as a symlink (ourexec 8>/9>would truncate the target) or
as a plain file thatclikae clean's GC reads as a dead lock — killing your
tmux sessionck-<name>andrm -f-ing your$HOME/.clikae/state/<name>.*. A
private dir removes the ability to plant, and sidesteps macOS's/tmppurge.
The GC now scans only the private dir and skips any malformed session id.
(DESIGN-tmux Rule 6 updated.) -
The tmux launch command no longer double-expands engine passthrough args.
The pane command was built asbash -c "$target_cmd", and since tmux runs it
viash -c, the outer quotes let the shell re-expand a passthrough arg carrying
$, a backtick, or a quote — and a backtick /$(…)was executed. It is now
single-quoted through a helper, soclikae claude x -- --foo '$(cmd)'reaches
the engine verbatim.
Fixed
-
Auto-carry after a mid-session limit created a session literally named
ck-._switch_supervise's same-engine relay used$tank_id, which was
local to a different function (run in a subshell) and thus empty — so the
carried session, its scrollback file, andCLIKAE_TANK_NAMEwere all wrong. -
clikae burn's reported exit code was always 0. The tmux wrapper's EXIT
trap read$?off a… | teepipeline with nopipefail, so therc=…in the
"real task failure" line reported tee's status, not the engine's. -
fleet_mcp_prelaunchrewrote a tank's.claude.jsonon every single
launch. Its no-op check byte-compared jq's reformatted output against the
on-disk file (jq reindents and drops the trailing newline), so it never matched
and the file's inode was replaced each time — racing any live session on the
same tank. The no-op is now decided semantically in jq. -
clikae renameorphaned a tank's burn-order entry and dry marker. Both key
the tank by name from outside its directory, so a rename silently dropped the
tank to the bottom of the board order and stranded its red-badge record. Now
carried across (both the env-adapter and agy rename paths). -
clikae rename/migrate/memoryno longer detach a symlinked dotfile.
Rewrites usedmv "$tmp" "$file", replacing a~/.zshrc(orAGENTS.md)
symlinked into a dotfiles repo with a detached 0600 regular file. They now write
through the file, preserving its inode, mode, and symlink. -
The home board's solo toggle now matches
clikae solo. Theskey only
flipped the marker file, leaving a shared tank in the "solo BUT STILL SHARING"
stateclikae memory statuscalls impossible; them→ isolate menu item
still called the retiredmemory isolate(a hard error). Both now delegate
to the realclikae soloverb, which also leaves/rejoins the Soul group. -
clikae to/relayrefuse a solo tank as an explicit target. grammar
§127 says a solo tank is never ato/relay target; auto-carry andmemory sharealready honored it, but a named target slipped through. Solo tanks are
also dropped from relay's target picker. -
memory shareno longerrm -rfs a prior own-memory stash. The$PWD-slot
path destroyed an existing.clikae-soul-stashbefore stashing, against the
"reversible, never lost" contract; it now uses a unique suffix like its siblings. -
The auto-resume nudge reaches the engine window, not the waiter's own pane.
wake_sittyped "go" into-t <session>(the current window) — which is the
wakecountdown window whenever the user was watching it, so the engine never
resumed. It now targets the first non-wakewindow explicitly. -
clikae watch's prompts no longer read their answer off the transcript. The
dry-detection loop pipedtail -finto the loop's stdin, so everyconfirm()
inside it (the wake opt-in, the "switch now?" / auto-consent) read the next
transcript line instead of the keyboard, and theexec clikae handoffhanded
the tail pipe to the started engine. The tail now reads on fd 3. -
The update-check tag from GitHub is sanitized to version characters before
it is printed or cached, so a tampered release name can't smuggle an escape
sequence to the terminal or a control byte into the cache.