Skip to content

fix: cross-platform Claude hook install + setup wizard hardening#50

Merged
skynetcmd merged 7 commits into
mainfrom
fix/chatlog-capture-status-and-hooks
Jun 15, 2026
Merged

fix: cross-platform Claude hook install + setup wizard hardening#50
skynetcmd merged 7 commits into
mainfrom
fix/chatlog-capture-status-and-hooks

Conversation

@skynetcmd

Copy link
Copy Markdown
Owner

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)

  • Interpreter resolves 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.
  • Replaced /bin/sh hook invocation (absent on native Windows) with direct interpreter invocation of the .py hook.
  • Emits the SessionStart chatlog capture-check hook (previously unmanaged/hand-edited).
  • New 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].
  • statusLine consent: never silently replaces a user's status line; default is to adopt m3's, preserving the prior one to a timestamped m3_prior_statusline_{YYYY.MM.DD}-{HH.MM.SS}.md sidecar (settings.json is strict JSON, so no inline // comments).

Setup wizard (bin/setup_memory.py)

  • Detects a Claude install and surfaces --install-claude as the recommended safe, re-runnable path.
  • Fix: forward-only migrations — apply .up.sql/bare NNN_*.sql, never .down.sql, ordered by integer prefix (was crashing fresh installs at migration 013).
  • Fix: fall back to requirements.txt when requirements-windows.txt is absent (was a FileNotFoundError on Windows).

Chatlog (bin/chatlog_status.py, bin/hooks/chatlog/*)

  • Truthful capture status + Stop-hook parse/scream fixes; SessionStart hook checks DATA (recent writes), not a config flag.

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

  • New tests/test_cross_platform_config.py (14 tests) locks in the OS-aware resolution; mutation-verified (reintroducing the Windows-leak or /bin/sh bug makes the suite fail). No real venv/DB touched.
  • End-to-end verified: fresh temp DB applies all 33 forward migrations (38 tables), Claude detected, --install-claude recommended, clean exit.
  • Idempotency verified: re-running the install is a no-op (no duplicate hooks); upgrades replace stale entries in place.

🤖 Generated with Claude Code

Gemini CLI and others added 7 commits June 13, 2026 07:55
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>
@skynetcmd skynetcmd merged commit dcc7e68 into main Jun 15, 2026
9 checks passed
@skynetcmd skynetcmd deleted the fix/chatlog-capture-status-and-hooks branch June 15, 2026 02:58
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