v0.33.0
Changed
- Changed: the SessionStart hook does less before it answers. Work whose result
the first turn does not need -- previous-session recovery, the capture-gap
check, the slug and case-divergence records, publishing the start-context
cache, and thebefore_session_startdispatch whengit_restore.enabledis
false and no third-party hook is registered -- now runs in a detached phase
after the memory context has been emitted. Rendering a memory file under 16KB
no longer forkscat,REMEMBER_ROOTno longer forksdirname, and
mkdir -pon an existingtmp/is skipped. Foreground subprocess count per
start drops from 49 to 29; on Git Bash, where a fork costs 10-50x what it
costs on Linux, that is most of the hook's own cost. Measured on CI: the
hook's traced cold run on windows-latest falls from 2.32s to 1.60s on a
baseline store and 2.87s to 2.16s on a 20MB one; on ubuntu-latest from 148ms
to 102ms. Part of #660. - Changed:
REMEMBER_DEFER=0runs that deferred phase inline instead. It
exists for tests that assert on one of those side effects and need it to have
happened by the time the hook exits; the default is deferred. Part of #660. - Fixed: two concurrent
dispatchcalls shared one capture file
(tmp/dispatch-stdout.$$--$$is the shell's pid and does not change in a
subshell), so the background dispatch's cleanup could delete the file the
foreground one was still writing and a plugin's injected context vanished
with no error on any channel. The capture files are now named per event.
Found while deferring thebefore_session_startdispatch for #660.
Fixed
-
Fixed the Windows Git-Bash benchmark's hook-dir probe: it invoked the literal
bash(which resolves to the WSL launcher and emits UTF-16LE/NUL-interleaved output, exit 127 on a missing distribution) instead ofresolve_bash()'s Git Bash, and its output decoding is now UTF-16LE-aware (#672). -
Fixed: the
handoff_mode: "per_session"degrade notice (#363) silently
stopped printing in default (in-project) storage mode when no session_id
reached the SessionStart hook. The notice fired only in external storage
mode or when a session_id resolved, so a user on default storage whose
per_session request quietly fell back to the sharedremember.mdgot no
indication of it. Fixes #686. -
Fixed: under a Turkish locale (
tr_TR, andaz_AZthe same way) no save ever completed.safe_evalinscripts/log.shmatches the pipeline'sKEY=VALUEoutput with^([A-Z_][A-Z0-9_]*)=, and[A-Z]is a POSIX bracket range matched by the locale's collation rather than by byte value -- Turkish collation does not placeIinsideA..Z, so every bridge variable whose name carries anI(EXTRACT_FILE,POSITION,SKIP_LINES) was silently dropped while the counts still arrived. The run passed its "0 exchanges" gate and died inbuild-promptonFileNotFoundError: '', thousands of times over.safe_evalnow matches underLC_ALL=Cfor the duration of the function, the same guardconfig()already carries in that file (#695). -
Fixed:
scripts/save-session.shnow stops at the bridge, with an error namingEXTRACT_FILE, instead of callingbuild-promptwith an empty path. Whatever the reason the extract step's output loses a variable, the report should name the crossing rather than surface as a Python traceback fromopen('')(#695). -
Fixed: a memory file whose size was never measured was emitted through bash's
readrather thancat._remember_wc_size_get_intoreturned0for a path it held no measurement for, and the render loop rewrote anything non-numeric to0as well, so_remember_emit_file's own "no usable size" arm was unreachable and the 16 KB threshold added in this same release was defeated in exactly the degraded case it was written for -- a 4 MB file on the session-start foreground path, measured at 24.1s on windows-latest. The batchedwc -ccan fail wholesale, which leaves every file unmeasured at once. An unmeasured size is now kept distinct from zero at all three of the getter's call sites, and each sayssize unknownrather than printing0 bytes-- or an empty( bytes)-- for a file nobody measured (#695). -
Fixed: the #695 source scanner credited a nested function's
local LC_ALL=Cto its enclosing function._function_spansclosed a function at the first}at column 0, and #660's deferred-phase work definescapture_was_seenat column 0 inside_remember_deferred_phase-- so 148 lines that hold no such declaration were reported as protected. Neither change contained the defect on its own; it existed only once both had landed. The parser now tracks nesting with a stack and attributes a declaration only to the lines it governs (#695). -
Fixed: the capture-gap notice's own tests raced the phase that writes it. #660 moved the check into the deferred phase, where it is the last thing that phase does, so a hook that has returned says nothing about whether the check has run -- the positive assertions failed intermittently (caught on
macos-latest3.10, green on the other eleven legs) and, worse, all six negative assertions passed just as well against a phase that had not reached the check yet or had died before it. The positives now poll on the default deferred path, which is what proves the phase reaches its end; the negatives run the phase inline, where "did not fire" is a verdict rather than a race (#695). -
Fixed: a config key containing an
Idisabled the config-data cache outright on a Turkish-locale host._remember_cfg_flatten_cache_valid_linevalidated each cache line with[[ =~ ^_RCFG_[A-Za-z0-9_]+= ]], and inside[[ =~ ]]a bracket range is matched by the locale's collation rather than by byte value -- Turkish collation does not placeIinsideA..Z. The line read as malformed, the cache was refused, and every hook fell back to re-reading and re-flattening the config on every invocation, permanently, with nothing said (#695). -
Fixed:
_remember_normalize_win_pathstopped recognising the Windows driveI:under the same collation, and itstr '[:lower:]' '[:upper:]'mapped a lower-casei:drive to a dottedİunder Turkish case rules -- a two-byte character where one ASCII letter has to go. Thetrneeded its ownLC_ALL=Cprefix rather than the function'slocal:localon a name the environment never exported leaves it unexported, so on a host whose language is set throughLANGalone the child process kept the user's locale (#695). -
Changed: eleven further bracket ranges across
scripts/andhooks.d/now match underLC_ALL=C, scoped to their own function. Measured on glibc, only[[ =~ ]]collates --casepatterns and${v//[!...]/}compare bytes -- so these were not broken, but one line makes them immune and the rule is now uniform. A new source scanner (tests/test_locale_ranges_695.py) fails the suite when a letter range appears outside anLC_ALL=Cscope, with a written reason required for each allowlisted exemption (#695).