fix: cross-platform Claude hook install + setup wizard hardening#50
Merged
Conversation
Three fixes to the chatlog capture-health path. A pretty-printed-JSON parse
bug in the Stop/PreCompact hook had been silently writing false-failure
fallback files for 9 days (1,485 of them, all redundant — ingest was actually
succeeding) while the status panel reported a permanent false alarm.
1. claude_code_precompact.py — run_ingest() scanned for a line startswith("{")
and json.loads'd that single line, but ingest emits MULTI-LINE pretty JSON,
so it always parsed the lone "{" and reported written=0 -> false failure.
Added _extract_last_json_object() (brace-depth walk, string-aware) to parse
the real object. Also: failures now surface via {"systemMessage"} on STDOUT
(harness always parses stdout-JSON) + non-zero exit, so the stderr scream can
no longer be swallowed on an ingest exit-0/wrote-0 case.
2. chatlog_status.py — the "hooks[*].enabled" field reported config-time wiring
(host_agents[*].enabled), not capture health; it reads false even when the
Stop-hook/MCP write path captures fine, producing a permanent false alarm for
the session-start check. Added _recent_write_count() + a truthful top-level
`capture` block {healthy, recent_rows, window_min} driven by actual recent
DB writes. hooks[*] now exposes `wired` (clarified) alongside `enabled`
(back-compat alias). Warnings now fire on real write-staleness, distinguish
query-error from real-zero, and stay quiet on a fresh empty DB.
3. session_start_capture_check.py (new) — SessionStart hook printing a plain
GREEN/RED capture line from real DB rows. DB path resolved portably via
m3_sdk.resolve_db_path / M3_HOME (no hardcoded user path).
Tests: 26/26 chatlog_status tests pass; 118 chatlog/hook/ingest tests pass.
(2 pre-existing unrelated failures in roundtrip/curator confirmed red on
baseline via stash, not introduced here.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r rerank wrapper Python wrapper over m3_core_rs.cosine_batch_maxpool_packed: per candidate blob, the MAX cosine over a set of anchor vectors. Packs anchors + candidates into flat f32 byte buffers and calls the Rust core (rayon, GIL-released); falls back to a pure-Python `max(cosine ...)` when the Rust symbol is absent (older wheel) or the FFI errors, so callers never fail on a stale core. Empty anchors -> all -1.0; wrong-length blob -> 0.0. Exported in __all__ (parity oracle). Tests: 341 util/parity/cosine tests pass; wrapper matches the pure-Python reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…merge Hooks run through a shell (Git Bash on Windows) where backslash paths are escaped away, so the prior backslash venv paths collapsed to "command not found". Fix at the source: generate_configs.py - Resolve interpreter to the repo's own venv, OS-aware (Scripts/python.exe on Windows, bin/python on macOS/Linux), always forward-slash so it survives a shell on every OS and from any working directory. - Replace /bin/sh hook invocation (absent on native Windows) with direct interpreter invocation of the .py hook. - Emit the SessionStart chatlog capture-check hook (was previously unmanaged and hand-edited). - New install_claude_settings(): idempotently merge hooks + statusLine + mcpServers into the live ~/.claude/settings.json. Re-runnable for upgrades — m3-owned entries are replaced in place (no duplicate/conflicting lines), user hooks and foreign MCP servers preserved, backup taken, diff shown, prompts before writing. CLI: --install-claude [--yes|--dry-run|--settings-path]. setup_memory.py - Detect a Claude Code install and surface --install-claude as the recommended safe, re-runnable path; manual paste kept as fallback. All paths derive from __file__ / expanduser(~); no hardcoded user paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Never silently replace a user's status line. install_claude_settings now:
- Adopts m3's statusline-command.sh only when none is set, when the current one
is already exactly ours (idempotent upgrade), or with consent. Differing
status lines prompt (default YES); --keep-status-line declines non-interactively.
- Before replacing, saves the prior statusLine JSON verbatim to a sidecar
m3_prior_statusline_{YYYY.MM.DD}-{HH.MM.SS}.md beside settings.json (local
time). settings.json is strict JSON with no // comments, so the prior config
is stashed in a file rather than inline-commented; the sidecar documents how
to restore it.
Default remains: adopt m3's developed status line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stall path) End-to-end verification of setup_memory.py surfaced two latent bugs that crash a fresh install (never hit before because the wizard had not been re-run from scratch on Windows): 1. Migrations applied .down.sql rollback files. The loop globbed *.sql sorted by filename, so "013_*.down.sql" ran before "013_*.up.sql" — applying a rollback for a migration that had not been applied yet (crash: no such column conversation_id). Now apply only .up.sql / bare NNN_*.sql, skip every .down.sql, and sort by the integer prefix so ordering is correct regardless of zero-padding or up/down interleaving. 2. requirements-windows.txt was referenced on Windows but does not exist, so the pip step would FileNotFoundError. Fall back to requirements.txt when the windows-specific file is absent. Verified: fresh temp DB applies all 33 forward migrations in order (38 tables, memory_items present), Claude install detected, --install-claude recommended, manual fallback printed, clean exit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lock in the OS-aware Claude-install behavior fixed on 2026-06-14 so it can't regress: - Windows uses .venv/Scripts/python.exe, macOS/Linux use .venv/bin/python (no .exe); generated commands are always forward-slash (shell-safe), never use /bin/sh, and always include the SessionStart capture-check hook. - POSIX output must not leak the Windows interpreter layout, and vice versa. - setup_memory.py: venv layout per OS, requirements.txt fallback when requirements-windows.txt is absent, and forward-only migration selection (skip .down.sql, order by integer prefix). Mutation-verified: reintroducing either the Scripts/python.exe-on-posix leak or the /bin/sh hook makes the suite fail with a clear message. 14 tests, no real venv/DB touched (os.name / os.path.exists are monkeypatched per test). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI Ruff failures on PR #50: - claude_code_precompact.py: drop dead `escaped = False` (the backslash handling was reimplemented inline; the flag was never read) — F841. - test_cross_platform_config.py: remove unused `import json`, sort imports — F401/I001. ruff check clean on all changed files; 14 cross-platform tests still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the m3 → Claude Code install path work correctly on Windows, macOS, and Linux, and hardens the setup wizard against two crash bugs that hit a fresh install. Also includes a chatlog capture-status fix and a reusable max-similarity rerank utility.
Root cause of the original failure: hook commands run through a shell (Git Bash on Windows), where backslash venv paths collapse to
command not found. Fixed at the source rather than per-machine.Changes
Cross-platform hook/config generation (
bin/generate_configs.py)Scripts/python.exeon Windows,bin/pythonon macOS/Linux), always forward-slash so it survives a shell on every OS and from any working directory./bin/shhook invocation (absent on native Windows) with direct interpreter invocation of the.pyhook.SessionStartchatlog capture-check hook (previously unmanaged/hand-edited).install_claude_settings()— idempotently merges hooks + statusLine + mcpServers into the live~/.claude/settings.json. Re-runnable for upgrades: m3-owned entries are replaced in place (no duplicate/conflicting lines), user hooks and foreign MCP servers preserved, backup taken, diff shown, prompts before writing. CLI:--install-claude [--yes|--dry-run|--keep-status-line|--settings-path].m3_prior_statusline_{YYYY.MM.DD}-{HH.MM.SS}.mdsidecar (settings.json is strict JSON, so no inline//comments).Setup wizard (
bin/setup_memory.py)--install-claudeas the recommended safe, re-runnable path..up.sql/bareNNN_*.sql, never.down.sql, ordered by integer prefix (was crashing fresh installs at migration 013).requirements.txtwhenrequirements-windows.txtis absent (was a FileNotFoundError on Windows).Chatlog (
bin/chatlog_status.py,bin/hooks/chatlog/*)Util (
bin/memory/util.py)_cosine_batch_maxpool_packed— max-cosine over multiple anchor vectors via the Rust core, with a pure-Python fallback.Testing
tests/test_cross_platform_config.py(14 tests) locks in the OS-aware resolution; mutation-verified (reintroducing the Windows-leak or/bin/shbug makes the suite fail). No real venv/DB touched.--install-clauderecommended, clean exit.🤖 Generated with Claude Code