Skip to content

v0.30.7

Choose a tag to compare

@github-actions github-actions released this 01 Sep 03:14

Fixed

  • Self-heal no longer takes over a user's own script that merely shares part of a hook's filename. Both the hook-removal path and the migration path that keeps an installed hook's command current used a plain substring check against markers like usage-terse-mode, so a user's own script — say, a backup named usage-terse-mode-backup.py — matched and was silently rewritten to the official command. The removal path was fixed first, requiring a path separator, quote, whitespace, or string boundary around the marker and a trailing .py; the same precise check is now used at the four remaining call sites in the migration logic that decide whether to overwrite a hook's command.
  • Language detection no longer misreads plain English words as Japanese or Korean, or a made-up Chinese-script tag as a real one. The _normalize_lang logic — duplicated across four files — matched ja/ko with an unbounded startswith, so jargon and koala were read as Japanese and Korean; a POSIX @modifier suffix wasn't stripped, so zh_TW@variant fell through to English; and zh-HK didn't accept further BCP 47 subtags the way zh-TW/zh-Hant did. A second pass found the same unbounded-prefix bug in zh-Hant/zh-Hans (letting a nonsense zh-Hantasy match as Traditional Chinese) and a missing zh-SG- prefix (dropping a legitimate zh-SG-x-private tag to English). All four duplicated copies now require a boundary after every prefix.
  • A non-UTF-8 terminal no longer crashes terse mode or session resume, and a truncated or corrupted version marker no longer wedges self-heal. The SessionStart/UserPromptSubmit hooks wrote JSON without ensure_ascii, so a terminal using an encoding like cp950 raised UnicodeEncodeError and lost the entire message; output is now forced to ASCII-safe JSON with the write wrapped in try/except OSError. The four version-marker readers used split and choked on a truncated line or invalid bytes, which meant a broken installed script was never recognized as needing reinstalling; they now use partition and also catch UnicodeDecodeError. Separately, a stale terse-mode sidecar whose content had rotted was never rebuilt as long as the hook version hadn't changed, because staleness was checked with a plain exists(); it's now checked by validating the sidecar's content.
  • Session resume's stdin and stdout failures are handled the same way its sibling hooks already were, and a corrupted newest session log falls back to an older one instead of going silent. _read_stdin_utf8() could raise OSError on a broken pipe, and unlike terse mode and the terse reminder, session resume didn't catch it — an I/O hiccup crashed the hook instead of exiting 0. Separately, _parse_session() caught OSError but not UnicodeDecodeError, so a session log with invalid UTF-8 bytes made the whole resume prompt disappear instead of falling through to the next older, valid log the way an empty log already did.
  • The health-check reminder's own state write no longer takes the whole resume prompt down with it, a bare punctuation reply is no longer read as a real request, and a newly created but uncommitted directory now shows up in the "not saved yet" list. Saving the health-check reminder's dedup state could raise OSError (a full disk, a permissions error) and that exception propagated out far enough to swallow the main resume prompt along with it; it's now scoped with contextlib.suppress(OSError). Separately, _has_structural_signal treated any punctuation anywhere in the text as a sign of real content, so short replies like ok. or /clear were kept as if they were substantive; it now requires alphanumeric characters on both sides of the mark. And git status's trailing slash on an untracked directory path made basename return an empty string, which was silently dropped, so a brand-new uncommitted folder never appeared in the uncommitted-work summary.
  • A todo list is now cleared once every item on it is marked done. The resume hook only overwrote its stored todo list when the new one was non-empty, so the last TodoWrite call of a session — the one that marks everything complete, leaving an empty list — was ignored, and the next session's resume prompt kept reporting work that was already finished.
  • The quota-restored notification fires again, and a warning now fires on the very first reading if quota is already past the threshold. Any reading below 100% cleared the "was depleted" flag, regardless of how small the drop was, so by the time a genuine reset was detected — a single-step drop large enough to distinguish a real period rollover from noise — the flag had already been cleared by an earlier, tiny fluctuation, and the restored notification never fired. It's now cleared only by that same reset detection. Separately, a warning threshold could only be crossed relative to a previous reading, so quota that was already above the threshold on the very first observation — for instance, the app launching mid-session at 95% — produced no warning at all, unlike the depleted notification, which always fires on a first reading of 100%.
  • A dead plist key was removed, and a Windows registry read failure can no longer take down the whole tray menu. StartDelay isn't a key launchd.plist(5) recognizes, so it had no effect and was doing nothing since it was added; it's gone. Separately, the Windows login-item's is_enabled() caught only FileNotFoundError, but it's called once per menu render to draw the "launch at login" checkbox — a registry permission error there propagated out and could break the whole tray menu instead of just that one row; it now catches any OSError and reports "not enabled."

Changed

  • Terse mode's plain-language rule now also covers text pasted verbatim from a subagent. The mode's own prompt only governed sentences Claude wrote itself, leaving no instruction for handling a subagent's report or a tool's raw output when quoted directly — jargon inside it went unexplained. A line was added after the existing "explain a term on first use" rule, in all five languages.