Repository navigation
claude-hooks v1.12.0
Added
-
/consultantsreview loop — Claude now critiques a council answer
and iterates, mirroring/get-advice's discuss-until-satisfied flow.
Previously the skill accepted the first council result and moved on
unless the user explicitly typedfollowup. A new engine-owned
consultancy status machine sits above the per-run status
(running|completed|failed): a freshaskopens a consultancy
(in_progress), a completed run flips it toready_to_review, and
the skill either marks itaccepted(newclaude-consultants accept <sid>verb +POST /v1/consult/{sid}/accept) or auto-issues a
focused follow-up and loops. The loop is bounded by a flat,
effort-independentmax_followupscap (default 4, enforced
server-side); a follow-up past the cap is refused with a structured
{"ok": false, "reason": "followup_limit_reached"}and the
consultancy entersawaiting_approval. The skill then asks the user,
who approves conversationally; Claude re-issues the follow-up with the
approval carrier--allow-extra N(--force= the configured
default), which raises the cap byNfor that consultancy only — no
persisted config change.--allow-extrais also the escape hatch for
non-Claude-Code/scripted callers. Two new config knobs, settable from
both the/consultants configmenu (Followup limit) and the CLI
(config set-max-followups,config set-allow-extra):max_followups
(cap, ≥ 0) andallow_extra(per-approval grant size, ≥ 1).
Consultancy state is persisted toconsultancy.jsonin the root
session dir (androot_sidtometadata.json) so the status is
queryable (it rides everystatus/result/state/follow-up
response under aconsultancyblock) and survives idle reap, daemon
restart, and context compaction — the skill resumes the loop by
reading the persisted status. The council graph is unchanged; this is
a layer above it. The default cap of 4 is a deliberate default-behavior
change (followups were previously unbounded) — the M12 parity baseline
records the new default andtests/test_consultants_review_loop.py
gates the end-to-end behavior.install.pygains an optional prompt to
tunemax_followups/allow_extraat install time. -
install.py allow-lists the memory/KG MCP servers so memory writes
stop being blocked by the auto-mode permission classifier. In
autopermission-mode Claude Code routes any tool not matched by a
staticpermissions.allowrule through a safety classifier (an LLM
call to api.anthropic.com); the memory/KG tools are writes, so the
classifier gated every store/KG mutation — and blocked them outright
("temporarily unavailable, auto mode cannot determine safety")
whenever that upstream was flapping. The installer now adds wildcard
allow-rules for all memory backends —
mcp__pgvector__*,mcp__sqlite_vec__*,mcp__qdrant__*,
mcp__memory_kg__*,mcp__memory__*— to~/.claude/settings.json
permissions.allowduring a normal install, making recall/store
auto-approve deterministically (rule precedence is deny → ask →
allow) without the classifier. Injection is additive, idempotent and
backed-up (_ensure_memory_allow_rules);uninstallremoves exactly
those wildcard rules and leaves any hand-added entries alone. New
install.py --sync-permissionsflag (re)applies only these rules and
exits — no provider probing, hook rewrite, or dialog — so an
already-installed host can be brought up to date safely. The
memory_kgprovider is covered under both its canonical key and the
memorykeymcp-server-memoryconventionally registers under.
Fixed
- Test isolation: config tests could write to the real
~/.claudeon
Windows, contaminating each other and clobbering the dev's live config
on the Windows bench. The home-isolation pytest fixtures
(isolated_home/tmp_claude_home) set only$HOME, which is a
silent no-op on Windows —pathlib.Path.home()/ntpath.expanduser
consult%USERPROFILE%(then%HOMEDRIVE%%HOMEPATH%), never$HOME.
Soconsultants.config.save_config(scope="user")wrote to the real
home, producing deterministic within-class contamination
(TestSetStoreDistillation::test_fallback_chain_add_remove_clear).
Centralized into one cross-platform helper,
tests/conftest.redirect_home, which setsHOME+USERPROFILE+
HOMEDRIVE+HOMEPATHtogether; the per-file HOME-only copies were
removed in favor of the shared conftest fixtures. Proof: the bench's
real config is SHA256 byte-identical before/after running the config
suite. (bug-635) - sqlite_vec: a second table on a db already migrated to the latest
schema was never created →no such tableat insert.
migrate_schema()gated all work behind a db-wide schema version
(if current >= LATEST_VERSION: return), but tables are per-name. Once
the first table carried a db file to LATEST, any later provider opening
the same file with a differenttableearly-returned before its
CREATE TABLEran. Latent in production (one table per db) but real,
and it broke the sqlite_vec integration tests whenever Ollama was
reachable (they share one db across tables). Now, at LATEST, the
requested table family is still ensured idempotently (new
_table_existsguard) while the common single-table case keeps its
zero-work fast return — the sqlite analog of the pgvector
"split create from migrate" fix. - PreCompact: the wrap-up summary was never written for long-lived
sessions, losing open + running items across a compact.
wrapup_synth.read_transcript()loaded the entire transcript into
memory on every compaction. A multi-day session's transcript grew to
581 MB / 184 k messages; under that session's heavy load the read +
synthesis exceeded the 20 s PreCompact hook timeout, so Claude Code
killed the hook beforewrite_to_diskran — no wrap-up file existed
and the post-compact recovery had nothing to surface (the reported
"forgot the open and running items after context compact"). The read
is now bounded to a trailing window (default 24 MB, configurable via
hooks.pre_compact.max_transcript_mb;0disables the cap). The
transcript is append-only, so the tail holds the recent working
window; synthesis now completes in ~0.3 s on the 581 MB case instead
of timing out. (bug-625) - stop_guard: stall check no longer false-positives on background
status prose. A commitment phrase preceded by a copula
("CD-IQ4_K_M is building now") is a third-person status report about
a background process, not a first-person commitment to call a tool. A
new copula guard (_commitment_hitinclaude_hooks/stop_guard.py)
discards copula-preceded matches; a genuine first-person commitment in
the same tail still fires.
Changed
- HyDE: raised the recall hook caps so the local fallback gets its full
cold-start window when the cloud primary stalls. The expansion runs a
sequential primary→fallback chain (gemma4:31b-cloudthen a local model),
each call getting the full per-modelhyde_timeout(30 s) — but the
UserPromptSubmit(15 s) andSessionStart(5 s) hook caps were SIGTERMing
the hook before the fallback could run, yieldingall models failedeven when
the local model was healthy (a cold start needs well over 10 s). Both caps are
now 65 s (= hyde_timeout × 2 + recall overhead) ininstall.py's
HOOK_TEMPLATE; the warm/cache path is unchanged (returns in ~0–4 s, the long
cap only bites on a genuine cloud stall). See
docs/hyde.md"Hook cap must cover the whole chain". - stop_guard: a genuine trailing question now backs the guard off.
When the assistant's turn ends with a question (?), both the
stall-after-commitment check AND the permission-seeking prose patterns
("should I continue?") are suppressed — forcing the model past a real
question is the unsafe choice and conflicts with "confirm before
destruction". Ownership-dodging / session-quitting nudges are
unaffected. Newsuppress_on_trailing_questionflag (default true)
underhooks.stop_guard. - wrap-up / PreCompact: full reconnect commands preserved across a
compact.wrapup_synthnow captures the entiresshinvocation
(port, user, key,-L/-Rtunnels) and records both the initial
and last-known-good connection (vast.ai / RunPod use case, where
the port is mandatory and was previously dropped). The reconnect
block is wrapped in machine-extractable sentinels and inlined into
the post-compact recovery context bywrapup_recovery— surviving
even if the model never opens the wrap-up file. Costs zero extra
tokens when the session touched no remote hosts. The/wrapupskill
§7 now instructs the model to preserve the same. - wrap-up / PreCompact: active background jobs now survive a compact
inline too. Section 6's running-items list (Monitor / ScheduleWakeup
/ CronCreate / background Bash) is wrapped in machine-extractable
sentinels and inlined into the post-compact recovery block by
wrapup_recovery(capped at 10 lines,+N morepointer for the
rest), mirroring the reconnect-command preservation above. Losing
"you left N background jobs running" across a compaction boundary was
half thebug-625failure. Sentinel-gated, so zero extra tokens when
no background work was running.