Skip to content

Hooks Reference

wrm3 edited this page Jul 6, 2026 · 1 revision

gald3r Hooks — Reference

Every gald3r lifecycle hook: when it fires, what it does, side effects, and how to configure or disable it. Extracted from each hook's .md companion (or the .ps1/.py header when none exists) under project_template/.claude/hooks/. Hooks are wired in .claude/hooks.json / .cursor/hooks.json.

Total hooks: 36


g-hk-agent-complete

What it does:

Python port of g-hk-agent-complete.ps1 (T1584). Agent/stop lifecycle hook (fires on the "stop" event). Reads the stop-event payload from stdin ({"status": ..., "loop_count": N, "conversation_id": ..., "transcript_path": ...}), writes diagnostic entries to .gald3r/logs/hook_diag.log, discovers a tran

Source: g-hk-agent-complete.py


g-hk-agent-worktree-janitor

Fires On:

Both SessionStart and Stop (Cursor sessionStart/stop, Claude Code SessionStart/Stop). Wired in .cursor/hooks.json and .claude/settings.json alongside the other session-boundary hooks. Each event runs at most once per session via an idempotency env-var guard (GALD3R_HK_WORKTREE_JANITOR_<EVENT>_APPLIED), so a SessionStart run and a Stop run in the same session are independent but neither re-fires within itself.

What It Does:

Delegates entirely to the absorbed engine verb gald3r worktree janitor (A1 / T1658), invoked with --apply --quiet so pruning actually runs but this hook stays silent unless something goes wrong. The underlying janitor:

  1. Scans .claude/worktrees/agent-* and .cursor/worktrees/agent-* for native background-agent worktrees (distinct from gald3r-owned worktrees under .gald3r-worktrees/, which gald3r worktree cleanup already handles).
  2. Classifies each as stale when its owning process (resolved from the git worktree lock reason's pid) is dead and the worktree has been idle past the threshold (GALD3R_JANITOR_STALE_HOURS, default 2h). A live owning process always protects the worktree regardless of age.
  3. Rescues dirty worktrees first: any uncommitted changes are committed to the worktree's own branch before removal — never force-discarded.
  4. Unlocks + force-removes the worktree directory, keeping the branch ref.
  5. Deletes the branch only if fully merged into main; unmerged branches are kept for triage.
  6. Optionally (only when GALD3R_JANITOR_REAP_PROCESSES=1) terminates orphaned claude/cursor processes past the same stale threshold that are not protecting any live worktree.

Side Effects:

  • Removes stale worktree directories (.claude/worktrees/agent-*, .cursor/worktrees/agent-*) and deletes their branch only if merged into main.
  • May create a rescue commit on a worktree's own branch before removing it.
  • Appends a structured summary line to .gald3r/logs/worktree_janitor.log on every run (counts: scanned/pruned/rescued/skipped/branches_deleted/branches_kept/ processes_terminated/errors) — this is the audit trail, independent of git history.
  • Process termination is opt-in and guarded; default behavior never kills anything.
  • Never blocks the host session — any error is caught and surfaced only via additional_context; the hook always returns {"continue": true}.
  • Idempotent: a repeat run finds nothing left to prune once cleaned.

Related Tasks:

  • T1592 — Auto-prune stale agent worktrees + orphaned claude.exe processes.
  • Core logic: the gald3r worktree janitor verb in the agent binary (A1 / T1658) (core scan/rescue/prune/reap logic, unit-tested against synthetic git repos).
  • Distinct from (does not replace) gald3r worktree cleanup, which only ever touches .gald3r-worktree.json-owned worktrees.

Source: g-hk-agent-worktree-janitor.py


g-hk-claude-chat-logger

Fires On:

The canonical stop event, indirectly via g-hk-agent-complete (T1624, WS-A-1). g-hk-agent-complete is the registered stop concern (g_hk_core.py CONCERN_CHAIN["stop"], .claude/settings.json hooks.Stop, .cursor/hooks.json stop); it extracts/discovers the transcript path (T1232 fallback included) and launches this logger as a subprocess. The indirect wiring is recorded machine-readably in g_hk_core.py INDIRECT_CONCERNS so the hook-parity lint (WS-A-5) treats it as chained, not orphaned. Registering this file directly as well would write every chat log twice. Launcher platform-map resolution polish is tracked in T1625 (BUG-133).

What It Does:

  1. g-hk-agent-complete reads the stop payload, resolves transcript_path + conversation_id/session_id (payload → env → T1232 agent-transcripts scan), and invokes g-hk-claude-chat-logger.py --transcript-path <path> --project-path <root> [--conversation-id <id>] ....
  2. This script reads the transcript JSONL and writes a human-readable transcript to .gald3r/logs/{YYYY-MM-DD}_{id}_{platform}_chat.log in the same format the Cursor logger produced.

Side Effects:

  • Writes .gald3r/logs/{date}_{session_id}_claude_chat.log (the transcript).
  • Appends diagnostic lines to .gald3r/logs/hook_diag.log (proves the hook ran; records success/exit code).
  • Never blocks or alters the Stop decision (emits {} and exits 0).
  • Does NOT touch tool-call logging, reflection hints, or the rest of the dormant Claude hook chain — that migration is tracked separately (see BUG-091 Related).

Related Tasks:

  • BUG-091 — Claude Code chat logging broken (Cursor-format hooks.json ignored; Cursor logger is DB-coupled). This hook is the chat-logging portion of the fix.

Source: g-hk-claude-chat-logger.py


g-hk-component-tag-check

Fires On:

Git pre-commit event. Inspects every staged file under .gald3r_sys/ at commit time. Not auto-wired to hooks.json — activated via git config core.hooksPath. See setup instructions below.

What It Does:

Scans staged .md files in skills/, commands/, agents/, rules/ for a subsystem_memberships: YAML frontmatter field, and staged .ps1 files in hooks/, scripts/ for a # @subsystems: comment in the first 15 lines. Blocks the commit (exit 1) if any staged file is missing its tag. Prints the violation list and the valid group names.

Side Effects:

  • No files written, no state changed — read-only scan
  • Exits 0 (allow) on clean or non-.gald3r_sys files
  • Exits 1 (block) on any untagged .gald3r_sys component file

Related Tasks:

  • T1458 — subsystem sprawl prevention enforcement
  • T1459 — aggregate_subsystems.ps1 aggregation script
  • Rule: g-rl-38 — component creation standards (always-applied)
  • Commands: @g-skill-new / @g-command-new / @g-rule-new / @g-create-hook / @g-agent-hire — scaffold correctly-tagged components

Source: g-hk-component-tag-check.py


g-hk-crash-record

Fires On:

  1. The canonical stop event (T1624, WS-A-1) — wired in g_hk_core.py CONCERN_CHAIN["stop"] and registered on the Claude Code / Cursor stop triggers with an explicit CLI declaration (--component-type hook --component-name stop-chain --trigger-source ...), recording one hook activation per agent turn when CRASH stats are enabled. Payload fields, when present, always win over the CLI declaration.
  2. A gald3r-internal CRASH activation report (the original path). The engine auto-records every Command it dispatches (gald3r.crash + adapters/cli.py); IDE harnesses (Cursor / Claude Code) do not emit a discrete event for every Rule / Skill / Agent / Hook activation, so this hook is the explicit path those use: a hook event, the gald3r skill/command runner, or an agent invokes it with a JSON payload describing the component that just activated. Rule "activation" has no native event (rules are always-loaded context), so a faithful "rule fired" signal must be reported here explicitly.

What It Does:

  • Zero-overhead gate first: if GALD3R_CRASH_STATS is unset / off, records nothing and returns immediately (matches the engine hot-path gate, AC #10).
  • Otherwise appends one JSONL line matching gald3r.crash.ActivationRecord: {component_type, component_name, activated_at, session_id, trigger_source, elapsed_ms}.
  • Non-blocking — always returns { continue = true }, never delays the observed event, never touches control-plane state (TASKS.md, BUGS.md, task/bug files).

Side Effects:

  • Appends one line to .gald3r/logs/crash_activations.jsonl.

Source: g-hk-crash-record.py


g-hk-encoding-normalize

Fires On:

The stop event (end of each agent turn) for every IDE target, and as a git pre-commit hook (-PreCommit). Also runs as a report-only verification scan (-Scan) for CI / pre-flight gates.

What It Does:

Normalizes git-dirty (or staged, or named) text files to the encoding that is correct for their file type and to LF line endings, after every agent turn. This is the systemic fix for the recurring Windows / PowerShell 5.1 corruption class (BUG-073 em-dash mangling, BUG-094 mojibake, and CRLF churn in parity sync).

Encoding policy (the T1428 distinction):

File type Target encoding Why
.ps1, .psm1, .psd1 (PowerShell) UTF-8 WITH BOM PS5.1 parses a BOM-less UTF-8 script as Windows-1252 and mangles every non-ASCII byte (em-dash, emoji). The BOM is REQUIRED for PS5.1 correctness (BUG-073).
Everything else (.md, .yaml, .json, .ts, .py, task/bug files, ...) UTF-8 no-BOM A BOM in markdown / JSON / source is itself the corruption that produces mojibake when other tools read the file.

All processed files are normalized to LF regardless of type.

Encodings detected and corrected:

  • UTF-8 with BOM (EF BB BF) -- stripped for non-PowerShell; preserved/added for PowerShell
  • UTF-16 LE (FF FE) -- converted to the correct UTF-8 variant for the extension
  • UTF-16 BE (FE FF) -- converted to the correct UTF-8 variant for the extension
  • CRLF / bare CR -- normalized to LF

File types processed: .md, .mdc, .yaml, .yml, .json, .ps1, .psm1, .psd1, .ts, .tsx, .js, .jsx, .py, .txt, .sh, .bash, .html, .htm, .css, .scss, .sql, .toml, .ini, .cfg, .gitattributes, .gitignore, .env, plus extensionless files under .gald3r/.

Binary files are skipped two ways: (1) by extension — only the text extensions above are considered; and (2) by content (T1447) — any file the BOM sniff reads as UTF-8/UTF-8-BOM that contains a NUL byte (0x00) is treated as binary/invalid-UTF-8 and left byte-identical, so a mislabeled binary with a text extension is never lossily rewritten. (UTF-16 files legitimately contain NULs and are detected by their BOM, so they still normalize correctly.)

Side Effects:

  • Rewrites dirty text files in-place (correct UTF-8 BOM state + LF endings).
  • In -PreCommit mode, re-stages normalized files with git add so the fix lands in the commit.
  • In -Scan mode, writes nothing; exits 1 if drift is found (clean = 0).
  • Prints a one-line summary per file changed (suppressed with -Quiet). Never blocks the turn.

Related Tasks:

  • T1428: Encoding Intercept Hook -- UTF-8 no-BOM + LF normalization (this hook).
  • BUG-073: PS5.1 em-dash / Unicode mangling (drives the .ps1 BOM exception).
  • BUG-094: mojibake / encoding corruption in framework files.

Source: g-hk-encoding-normalize.py


g-hk-ggo-stop-detect

Fires On:

The stop event (Cursor stop / Claude Code Stop). Wired in .cursor/hooks.json and .claude/hooks.json under stop, alongside g-hk-agent-complete and g-hk-nightly-learn. Receives the stop JSON payload on stdin (guarded with [Console]::IsInputRedirected). The hook is a pure no-op (allow exit) unless a g-go-go run-state marker is present, active, AND owned by the same platform and session — so it never interferes with ordinary, non-autopilot sessions, and never blocks a different agent's stop event.

What It Does:

Detects the calling platform from $PSScriptRoot (.cursor/hookscursor, .claude/hooksclaude) and extracts the current session_id from the stop event stdin payload (session_id field, or derived from transcript_path).

Reads the g-go-go run-state marker .gald3r/logs/ggo_run_state.json (written by the g-go-go command at INIT and refreshed each iteration) and decides:

  1. No marker / not active → allow exit (no-op).
  2. Platform mismatch (stored.platform ≠ calling platform) → allow exit. The stored run belongs to a different agent (e.g. a Cursor agent does not block a Claude Code g-go-go loop, and vice versa).
  3. Session mismatch (stored.session_id ≠ current session) → allow exit. A fresh chat session is never forced to resume a prior session's run.
  4. authorized_hard_stop populated → a genuine hard-stop row was recorded; allow exit and clear the marker. Genuine hard stops are NEVER re-invoked.
  5. budget_remaining <= 0 → budget cap is itself a hard stop; allow exit.
  6. reinvoke_count >= min(budget_remaining, 25) → anti-infinite-loop fail-safe; allow exit.
  7. Otherwise (unauthorized mid-loop stop by the owning session) → increment reinvoke_count and emit a re-invoke decision (decision:block for Claude / continue:false+followup for Cursor) carrying a verbatim reminder of the forbidden stop reasons, forcing the loop to resume.

Side Effects:

  • Updates reinvoke_count and updated_at in .gald3r/logs/ggo_run_state.json on each re-invoke (case 6).
  • Writes session_id (and backfills platform if absent) on first-touch registration.
  • Removes the run-state marker on authorized hard stop, budget exhaustion, or re-invoke-cap exit (cases 3–5).
  • Appends diagnostic lines to .gald3r/logs/hook_diag.log (includes platform tag).
  • On case 6 only, returns a block/continue stop decision (holds the run open); in every other case returns { continue = true } and exits 0 (allows the stop).
  • Never blocks tool calls, never touches .gald3r/ control-plane state files (TASKS.md, BUGS.md, task/bug files).

Related Tasks:

  • T1444 — robust context-panic enforcement (stop-detection re-invoke hook + --context-aware throttle). This hook is Fix Direction #2.
  • BUG-107 — g-go-go context-panic stops disguised as session checkpoints. Spec hardening (Fix Direction #1) lives in commands/g-go-go.md; this hook is the mechanical enforcement layer that the bug requires before it can close.
  • Companion: commands/g-go-go.md (documents the run-state marker schema, the --context-aware flag for Fix Direction #3, and the re-invoke contract).

Source: g-hk-ggo-stop-detect.py


g-hk-graph-update

Fires On:

The canonical stop event (T1624, WS-A-1). Wired in g_hk_core.py CONCERN_CHAIN["stop"] and registered directly on the Claude Code (.claude/settings.json hooks.Stop) and Cursor (.cursor/hooks.json stop) triggers, so the graph refreshes at the end of every agent turn. It remains directly invocable as a git post-commit hook (its original T1158 role).

What It Does:

Locates the muninn indexers (docker/gald3r/tools/plugins/muninn/indexers/) and runs them incrementally — the Python AST indexer via python, the TypeScript indexer via node. Installs without the muninn plugin (the common case) skip in milliseconds. Each indexer run is capped at 60 seconds so a wedged indexer can never stall the host session's stop chain.

Side Effects:

  • Updates the muninn graph index files under the plugin directory.
  • Appends a muninn-update | ... line to .gald3r/logs/muninn_updates.log (only when .gald3r/logs/ exists).
  • Always exits 0 — never blocks the host session or a commit.

Related Tasks:

  • T1624 (WS-A-1) — wired the logging chain into the canonical hook core.
  • T1158 — muninn post-commit graph refresh (original role).
  • Skill: g-skl-muninn (graph queries this index serves).

Source: g-hk-graph-update.py


g-hk-nightly-learn

What it does:

Python port of g-hk-nightly-learn.ps1 (T1584). Nightly hook: trigger session summary extraction into learned-facts.md (T928, T1233). Fires under stop (agent session complete). Lightweight by design: 1. Walks up to find the project root. 2. Reads the per-N-sessions counter at `.gald3r/logs/learn-co

Source: g-hk-nightly-learn.py


g-hk-on-session-end

What it does:

Canonical session-end event entrypoint (T424). Thin trigger shim: delegates to the shared canonical event core (g_hk_core.dispatch). Contains NO business logic. Platform triggers point here so every harness fires the SAME shared core.

Source: g-hk-on-session-end.py


g-hk-on-session-start

What it does:

Canonical session-start event entrypoint (T424). Thin trigger shim: delegates to the shared canonical event core (g_hk_core.dispatch). Contains NO business logic — behavior lives in the core and the concern chain it runs. Platform triggers point here so every harness fires the SAME shared core.

Source: g-hk-on-session-start.py


g-hk-on-stop

What it does:

Canonical stop event entrypoint (T424). Thin trigger shim: delegates to the shared canonical event core (g_hk_core.dispatch). Contains NO business logic. Fires when the agent finishes responding to a turn (distinct from session-end).

Source: g-hk-on-stop.py


g-hk-on-tool-end

Fires On:

The canonical tool-end lifecycle event — after a tool/action completes. Mapped from each platform's native event by g_hk_core.PLATFORM_EVENT_MAP (Cursor postToolUse, Claude PostToolUse, kiro-cli postToolUse, …).

What It Does:

Calls g_hk_core.dispatch("tool-end"), which reads the harness payload once from stdin, runs the event's concern chain (currently empty — a clean pass-through that platforms can now fire), merges any additional_context, and emits a single { "continue": true } envelope.

Side Effects:

  • None of its own. Side effects come only from concern hooks registered in g_hk_core.CONCERN_CHAIN["tool-end"] (none yet).
  • Always returns { "continue": true } and exits 0 — never blocks.

Related Tasks:

  • T424 — Canonical event set + shared-core handlers. This is the tool-end canonical entrypoint.
  • Shared core: g_hk_core.py (dispatch, CANONICAL_EVENTS).

Source: g-hk-on-tool-end.py


g-hk-on-tool-start

What it does:

Canonical tool-start event entrypoint (T424). Thin trigger shim: delegates to the shared canonical event core (g_hk_core.dispatch). Contains NO business logic. This is the canonical blocking guard point — the core returns exit code 2 when a concern hook blocks the tool call (honored by Cursor pr

Source: g-hk-on-tool-start.py


g-hk-on-user-prompt-submit

Fires On:

The canonical user-prompt-submit lifecycle event — the user submits a prompt, before the agent acts on it. Mapped from each platform's native event by g_hk_core.PLATFORM_EVENT_MAP (Cursor beforeSubmitPrompt, Claude UserPromptSubmit, kiro-cli userPromptSubmit, …).

What It Does:

Calls g_hk_core.dispatch("user-prompt-submit"), which reads the harness payload once from stdin, runs the event's concern chain (currently empty — a clean pass-through that platforms can now fire), merges any additional_context, and emits a single { "continue": true } envelope.

Side Effects:

  • None of its own. Side effects come only from concern hooks registered in g_hk_core.CONCERN_CHAIN["user-prompt-submit"] (none yet).
  • Always returns { "continue": true } and exits 0 — never blocks.

Related Tasks:

  • T424 — Canonical event set + shared-core handlers. This is the user-prompt-submit canonical entrypoint.
  • Shared core: g_hk_core.py (dispatch, CANONICAL_EVENTS).

Source: g-hk-on-user-prompt-submit.py


g-hk-policy-check

What it does:

gald3r policy-as-code guardrail hook (T1611, D12). Concern hook registered in g_hk_core.py's tool-start chain (and invoked directly by g-hk-pre-commit.py for the git-level check). Evaluates the incoming tool-call payload against the active org policy bundle by calling the absorbed engine verb

Source: g-hk-policy-check.py


g-hk-post-session-trace

Fires On:

The canonical stop and session-end events. Wired in g_hk_core.py CONCERN_CHAIN — plain on stop (per agent turn), with --finalize on session-end — and registered directly on the harness-native Stop/stop trigger for Claude Code (.claude/settings.json) and Cursor (.cursor/hooks.json). The payload arrives on stdin as JSON; session_id (Claude), conversation_id (Cursor), and cwd/project_path are all accepted. The former gald3r-internal post_session event name is retired (D-8).

What It Does:

Reads the start marker staged by g-hk-pre-session-trace (.gald3r/logs/session_trace_<session>.json), computes elapsed milliseconds, and appends a duration line. On stop (default) the marker is KEPT so every turn logs the cumulative session duration; with --finalize (session-end) the marker is removed after logging. If no start marker is present it logs elapsed_ms=unknown. Non-blocking by design.

Side Effects:

  • Reads .gald3r/logs/session_trace_<session>.json; deletes it only when run with --finalize.
  • Appends a stop | session=... | elapsed_ms=... (or session-end | ... when finalizing) line to .gald3r/logs/session_lifecycle.log.
  • Always returns { continue = true } and exits 0 — never blocks, never touches control-plane state (TASKS.md, BUGS.md, task/bug files).

Related Tasks:

  • T1624 (WS-A-1) — wired the logging chain into the canonical hook core; retired the internal pre_session/post_session event names (D-8).
  • T1055 — original plugin lifecycle hooks (this was the post_session example).
  • Companion: g-hk-pre-session-trace (opens the session trace).
  • Pattern: commands/g-create-hook.md (event list + scaffolding contract).

Source: g-hk-post-session-trace.py


g-hk-post-skill-timing

Fires On:

The gald3r-internal post_skill lifecycle event, immediately after a gald3r skill body finishes. Like pre_skill, post_skill is a gald3r-internal lifecycle point (no native Cursor / Claude Code skill-boundary event exists), dispatched by the gald3r skill/command runner or fired manually, and not auto-wired into hooks.json. The payload arrives on stdin as JSON and SHOULD carry skill_name, skill_path, and timestamp.

What It Does:

Reads the start marker staged by g-hk-pre-skill-timing (.gald3r/logs/skill_timing_<skill>.json), computes elapsed milliseconds, removes the marker, and appends a timing line. If no start marker is present it logs elapsed_ms=unknown. Non-blocking by design.

Side Effects:

  • Reads and then deletes .gald3r/logs/skill_timing_<skill>.json.
  • Appends a post_skill | skill=... | elapsed_ms=... line to .gald3r/logs/skill_lifecycle.log.
  • Always returns { continue = true } and exits 0 — never blocks, never touches control-plane state (TASKS.md, BUGS.md, task/bug files).

Related Tasks:

  • T1055 — Add plugin lifecycle hooks (pre/post skill/session). This is the post_skill reference example.
  • Companion: g-hk-pre-skill-timing (opens the timing record).
  • Pattern: commands/g-create-hook.md (event list + scaffolding contract).

Source: g-hk-post-skill-timing.py


g-hk-pre-commit

What it does:

Python port of g-hk-pre-commit.ps1 (T1584). gald3r pre-commit sanity hook (opt-in). Checks staged changes for: secrets (BLOCK), staged .env files (BLOCK), large files >5 MB (WARN), C-026 worktree TASKS.md writes (BLOCK), gald3r task sync drift (WARN), protected files per g-rl-02 (BLOCK), and bare st

Source: g-hk-pre-commit.py


g-hk-pre-push

What it does:

gald3r optional pre-push gate hook (opt-in). Delegates to the absorbed engine verb gald3r push-gate --hook-mode (A1 / T1658) and exits with the gate's exit code. Release checks run only when GALD3R_RELEASE_PUSH=1 (or true) — that logic lives inside the gate verb, not here. The engine binary is res

Source: g-hk-pre-push.py


g-hk-pre-session-trace

Fires On:

The canonical session-start event. Wired in g_hk_core.py CONCERN_CHAIN["session-start"] (all dispatcher-driven platforms) and registered directly on the harness-native trigger for Claude Code (.claude/settings.json hooks.SessionStart) and Cursor (.cursor/hooks.json sessionStart). The payload arrives on stdin as JSON; session_id (Claude), conversation_id (Cursor), and cwd/project_path are all accepted. The former gald3r-internal pre_session event name is retired (D-8).

What It Does:

Parses the session-event payload (falling back to a timestamp-derived session_id when none is supplied), resolves the project root, prunes stale trace markers (older than 7 days), and stages a per-session start marker (.gald3r/logs/session_trace_<session>.json) with the start timestamp and an epoch-ms stamp. The companion g-hk-post-session-trace reads it to compute session duration on stop / session-end. Non-blocking by design.

Side Effects:

  • Writes .gald3r/logs/session_trace_<session>.json (start marker).
  • Prunes session_trace_*.json markers older than 7 days.
  • Appends a session-start | session=... | project=... line to .gald3r/logs/session_lifecycle.log.
  • Always returns { continue = true } and exits 0 — never blocks session start, never touches control-plane state (TASKS.md, BUGS.md, task/bug files).

Related Tasks:

  • T1624 (WS-A-1) — wired the logging chain into the canonical hook core; retired the internal pre_session/post_session event names (D-8).
  • T1055 — original plugin lifecycle hooks (this was the pre_session example).
  • Companion: g-hk-post-session-trace (logs/closes the session trace).
  • Pattern: commands/g-create-hook.md (event list + scaffolding contract).

Source: g-hk-pre-session-trace.py


g-hk-pre-skill-timing

Fires On:

The gald3r-internal pre_skill lifecycle event, immediately before a gald3r skill body executes. pre_skill is a gald3r-internal lifecycle point, not a native Cursor / Claude Code harness event — neither IDE exposes a skill-boundary event. It is dispatched by the gald3r skill/command runner or fired manually, and is therefore not auto-wired into hooks.json under a harness event name (the _doc.gald3r_lifecycle_events block in hooks.json documents this distinction). The payload arrives on stdin as JSON and SHOULD carry skill_name, skill_path, and timestamp.

What It Does:

Parses the skill-event payload, then stages a per-skill start marker (.gald3r/logs/skill_timing_<skill>.json) recording the start timestamp and an epoch-ms stamp. The companion g-hk-post-skill-timing hook reads this marker to compute elapsed time. Non-blocking by design.

Side Effects:

  • Writes .gald3r/logs/skill_timing_<skill>.json (start marker, consumed by the post_skill hook).
  • Appends a pre_skill | skill=... | path=... line to .gald3r/logs/skill_lifecycle.log.
  • Always returns { continue = true } and exits 0 — never blocks skill execution, never touches control-plane state (TASKS.md, BUGS.md, task/bug files).

Related Tasks:

  • T1055 — Add plugin lifecycle hooks (pre/post skill/session) to the gald3r hooks system. This is the pre_skill reference example.
  • Companion: g-hk-post-skill-timing (closes the timing record).
  • Pattern: commands/g-create-hook.md (event list + scaffolding contract).

Source: g-hk-pre-skill-timing.py


g-hk-pre-tool-call

Fires On:

The PreToolUse (Claude Code) / preToolUse (Cursor) event, before each tool call. Wired in .claude/hooks.json (PreToolUse) and .cursor/hooks.json (preToolUse) with matcher Bash|Shell|Terminal|run_terminal_cmd (shell-output tools). Receives the upcoming tool-call JSON on stdin. Always non-blocking — it NEVER denies a tool call; it only annotates with additional_context.

What It Does:

Inspects the event payload for a large stdout/stderr/output text block (probing output, stdout, stderr, tool_output, result, text at top level and on tool_input / tool_response). If a block exceeds N lines, it preserves the FULL block to .gald3r/logs/tool_output_<session_id>.log, then returns a compressed form as additional_context: a summary prefix (... [<total> lines compressed, last <N> shown -- run ID: <id>] ...), any error/warning "signal" lines lifted out of the truncated region, and the last N lines. N is read from .gald3r/config/AGENT_CONFIG.md field pre_tool_call_compress_lines (default 50; 0 = disabled).

Side Effects:

  • Appends the full pre-compression output block to .gald3r/logs/tool_output_<session_id>.log (non-destructive preservation).
  • Returns { "permission": "allow", "additional_context": "<compressed>" } when it compresses; { "permission": "allow" } (pure no-op) when disabled, on short output, on empty/unparseable stdin, or when no output field is present.
  • Never blocks tool calls. Never touches .gald3r/ control-plane state files (TASKS.md, BUGS.md, task/bug files).
  • Achievable-scope gap (documented): a PowerShell PreToolUse hook cannot retroactively rewrite terminal output blocks already rendered in the agent's context window — only the harness splices additional_context. This hook compresses whatever output the harness supplies on the event payload (prior or preview output); where the harness exposes no output field, the hook is a safe no-op. Full lossless capture always lands in .gald3r/logs/.

Related Tasks:

  • T1106 — Add pre_tool_call shell output compression hook (this hook).
  • IDEA-HARVEST-191 — source pattern (awesome-hermes-agent).
  • Related: IDEA-HARVEST-177 (/compress command), IDEA-HARVEST-166 (JSONL logging).
  • Config: .gald3r/config/AGENT_CONFIG.md field pre_tool_call_compress_lines.

Source: g-hk-pre-tool-call.py


g-hk-pre-tool-call-gald3r-guard

What it does:

Python port of g-hk-pre-tool-call-gald3r-guard.ps1 (T1584). Pre-tool-call guard: refuse unsupervised Edit/Write to .gald3r/ paths. Enforces g-rl-33 ".gald3r/ Folder Gate (HARD RULE)": "NEVER read or write any file inside .gald3r/ without an active gald3r agent." Hook contract (per Claude Code / Curs

Source: g-hk-pre-tool-call-gald3r-guard.py


g-hk-pre-tool-call-member-gald3r-guard

What it does:

Python port of g-hk-pre-tool-call-member-gald3r-guard.ps1 (T1584). Pre-tool-call guard: refuse Edit/Write to a Workspace-Control member repository's .gald3r/ that targets anything other than the marker pair (.identity / PROJECT.md). Enforces g-rl-36 "Workspace-Control Member .gald3r/ Marker-Only G

Source: g-hk-pre-tool-call-member-gald3r-guard.py


g-hk-pre-tool-call-prd-freeze

What it does:

Python port of g-hk-pre-tool-call-prd-freeze.ps1 (T1584). Pre-tool-call guard: refuse Edit/Write to a PRD file whose YAML status is released or superseded (C-019 / g-rl-33 § "PRD Freeze Gate"). A frozen PRD is the audit-of-record. Only @g-prd-revise may touch it, which creates a successor PRD an

Source: g-hk-pre-tool-call-prd-freeze.py


g-hk-session-end

What it does:

Python port of g-hk-session-end.ps1 (T1584). Session-end hook (T1057): records structured session-end metadata and stages a memory-capture pending marker for the next agent session to action. Fires under the Cursor "stop" event alongside g-hk-agent-complete and g-hk-nightly-learn. Unlike those sibli

Source: g-hk-session-end.py


g-hk-session-start

What it does:

Python port of g-hk-session-start.ps1 (T1584). Session-initialization hook (fires when a new composer conversation is created). Ensures platform dirs are populated via setup_gald3r_project, guards against double-application per session, reads and auto-heals .gald3r/.identity (user_id fallback from t

Source: g-hk-session-start.py


g-hk-setup-user

Fires On:

Manual invocation only — run once from a terminal:

python .claude/hooks/g-hk-setup-user.py

Never wired to settings.json / hooks.json / CONCERN_CHAIN (interactive).

What It Does:

Suggests a user ID (git global email → Cursor cached email → OS username), prompts with a [1] default hint, and stores the chosen ID in the ONE unified per-user identity record (<gald3r-home>/user_config.json; %LOCALAPPDATA%/gald3r on Windows, ~/.config/gald3r on POSIX). Extra setup fields (mcp_url, platform, setup_completed, setup_date, created_by) go to a separate setup_meta.json sidecar. A pre-existing legacy ~/.gald3r identity file is migrated in once, never regenerated.

Side Effects:

  • Writes/updates <gald3r-home>/user_config.json (identity) and <gald3r-home>/setup_meta.json (setup metadata).
  • Drops a .migrated-to-unified-home breadcrumb next to a migrated legacy file.
  • Never touches project state (.gald3r/), never blocks anything.

Related Tasks:

  • T627 — reconcile onto the unified home (retired the ~/.gald3r identity file).
  • T530/T531 — unified per-user identity record (gald3r.user_config / gald3r.home).
  • T1624 (WS-A-1, D-7) — orphan disposition: kept with this justification.
  • Engine test: .gald3r_sys/engine/tests/test_setup_user_hook_t627.py.

Source: g-hk-setup-user.py


g-hk-validate-shell

What it does:

Python port of g-hk-validate-shell.ps1 (T1584). Hook for shell command validation: blocks dangerous destructive commands before they execute. Hook contract: stdin : JSON { command, ... } exit 0 : allow (body: { "permission": "allow" }) exit 2 : deny (body: { permission: "deny", user_message, agent_m

Source: g-hk-validate-shell.py


g-hk-vault-migrate

Fires On:

The canonical session-start event with --if-diverged (T1627, WS-A-4). Wired in g_hk_core.py CONCERN_CHAIN["session-start"] and registered directly on the Claude Code (.claude/settings.json hooks.SessionStart) and Cursor (.cursor/hooks.json sessionStart) triggers. In that mode it fires ONLY on vault_location divergence: g-hk-vault-resolve must report VaultMigrationCandidate (the local vault holds markdown notes while a different shared vault is configured and writable); otherwise it no-ops with a [SKIP] line. Manual invocation without --if-diverged migrates unconditionally (-SourcePath / -DestinationPath / -Force).

What It Does:

  1. Consults the g-hk-vault-resolve.py sibling for source/destination defaults and the divergence signal.
  2. Merges log.md files block-wise (## headings, dedup, destination blocks first).
  3. Copies other files when missing at the destination, skips SHA256-equal files, and resolves conflicts by frontmatter date: (or mtime) — newer-or-equal source wins; an older source is kept at the destination and reported as a conflict. -Force always overwrites.
  4. Triggers g-hk-vault-reindex.py for the destination so both index artifacts reflect the migrated content.

Side Effects:

  • Writes/overwrites notes in the destination vault; merges log.md.
  • Regenerates the destination's _index.yaml + index.md views.
  • Session-idempotent via GALD3R_HK_VAULT_MIGRATE_APPLIED (-ForceRun bypasses). Never crashes the host session.

Related Tasks:

  • T1627 (WS-A-4) — registered the vault chain; added the divergence gate.
  • T1584 — Python port of the original .ps1 hook.
  • T1600 — removed the .ps1 fallback branches.

Source: g-hk-vault-migrate.py


g-hk-vault-reindex

Fires On:

The canonical stop event, debounced (T1627, WS-A-4). Wired in g_hk_core.py CONCERN_CHAIN["stop"] and registered directly on the Claude Code (.claude/settings.json hooks.Stop) and Cursor (.cursor/hooks.json stop) triggers — deliberately AFTER raw-inbox-watcher --hook-mode, so notes the watcher routes into the vault land in the same regen. Also invoked by g-hk-vault-migrate.py after a migration, and manually with -VaultOverride <path> / -ForceRun.

What It Does:

  1. Resolves the vault via g-hk-vault-resolve.py (or -VaultOverride) and scans *.md notes recursively — minus hidden directories (any path component below the vault root starting with .: .obsidian/, .git/, .cursor/, .gald3r_sys/, .backups/, ... are framework infrastructure, never vault notes; T1632 / WS-B-9) and the reserved index/log/schema files (matched case-insensitively, including the legacy _INDEX.md name).
  2. Debounce: skips the regen when both artifacts exist, the recorded note count matches, no legacy _INDEX.md MOC views await unification, and no note is newer than _index.yaml — rapid successive Stops do not trigger redundant regens. -ForceRun bypasses.
  3. Writes _index.yaml (vault root, UTF-8 no BOM) — the machine source of truth: path, title, type, ingestion_type, date, tags, source, project_id, refresh metadata per note.
  4. Writes an OKF-style index.md per directory (vault root + every directory holding notes): no frontmatter, # Section headings, * [Title](relative-url) - one-line description bullets. # Sections links down to child-directory indexes (progressive disclosure), # Notes lists the directory's own notes, and the root adds # Recent Updates. index.md is a derived view and never replaces _index.yaml.
  5. Deletes stale generated per-directory index.md views (identified by their Auto-generated by g-hk-vault-reindex marker line) from directories that no longer hold notes, plus legacy per-subdir _INDEX.md MOC views left behind by the retired gen_vault_moc.py script (identified by their gen_vault_moc / auto_generated: true markers) — their per-subdir index function is unified into this ONE generator's index.md views (T1632 / WS-B-9). Hand-written index.md and _INDEX.md files are never touched; hidden directories are never cleaned.

Side Effects:

  • Rewrites {vault}/_index.yaml and the per-directory index.md views.
  • Removes marker-carrying stale index.md views and legacy _INDEX.md MOC views.
  • Never crashes or blocks the host session (errors exit 0).

Related Tasks:

  • T1627 (WS-A-4) — registered the vault chain; dual-index OKF amendment.
  • T1632 (WS-B-9) — hidden-dir exclusion + legacy _INDEX.md unification; personal-vault rerun consuming this fixed generator.
  • T1634 (WS-B-11) — vault lint that validates the index.md format.
  • T1584 — Python port of the original .ps1 hook.

Source: g-hk-vault-reindex.py


g-hk-vault-resolve

Fires On:

The canonical session-start event (T1627, WS-A-4). Wired in g_hk_core.py CONCERN_CHAIN["session-start"] and registered directly on the Claude Code (.claude/settings.json hooks.SessionStart) and Cursor (.cursor/hooks.json sessionStart) triggers. Also imported in-process by the sibling vault hooks (g-hk-vault-reindex.py, g-hk-vault-migrate.py, raw-inbox-watcher.py) as their path resolver — the Python analogue of dot-sourcing the retired .ps1.

What It Does:

  1. Reads vault_location / repos_location from .gald3r/.identity, with .env fallbacks (GALD3R_VAULT_LOCATION / GALD3R_KNOWLEDGE_WELL_PATH / GALD3R_REPOS_LOCATION).
  2. Falls back to the local .gald3r/vault/ and .gald3r/repos/ when the shared location is unset, {LOCAL}, or not writable.
  3. Flags VaultMigrationCandidate when the local vault holds markdown notes while a different shared vault is configured — the divergence signal g-hk-vault-migrate --if-diverged gates on.
  4. Ensures the project's projects/<name>/sessions|decisions vault directories exist.

Side Effects:

  • Creates the local fallback and resolved vault/repos directories plus the per-project vault subtree when missing (idempotent mkdir -p semantics).
  • Prints nothing in standalone mode; never blocks the session (always exits 0).

Related Tasks:

  • T1627 (WS-A-4) — registered the vault chain + raw-inbox watcher.
  • T1584 — Python port of the original .ps1 hook.
  • Consumers: g-hk-vault-reindex.py, g-hk-vault-migrate.py, raw-inbox-watcher.py, g-hk-session-start.py (inline port).

Source: g-hk-vault-resolve.py


g-hk-vault-verify

Fires On:

The canonical stop event (T1627, WS-A-4). Wired in g_hk_core.py CONCERN_CHAIN["stop"] and registered directly on the Claude Code (.claude/settings.json hooks.Stop) and Cursor (.cursor/hooks.json stop) triggers, so a broken/partial vault surfaces at the end of every agent turn. Its banner logic is also sourced inline by g-hk-session-start.py while building the Vault Context banner, and it can still be run standalone (uv run python g-hk-vault-verify.py) to print the status line.

What It Does:

  1. Reads the configured vault_location directly from .gald3r/.identity (the declared value, not a resolved/auto-created path).
  2. Skips silently when vault_location is absent or {LOCAL} (local fallback) — there is nothing centralized to verify.
  3. Emits one status line:
    • Vault at {path}: OK — root exists and all research/ subdirs present.
    • Vault at {path}: NOT FOUND — configured path does not exist; offers @g-vault init.
    • Vault at {path}: PARTIAL (missing: ...) — root exists but research/ or one of its subdirs (articles, github, harvests, papers, platforms, videos) is missing; offers @g-vault init.

The expected research/ subdir set mirrors the canonical vault layout documented in skills/g-skl-vault/SKILL.md.

Side Effects:

  • None. Read-only. Fail-soft: any error returns an empty banner.
  • Always exits 0 in standalone mode and never blocks session start (warning only).

Related Tasks:

  • T1627 (WS-A-4) — registered the vault chain on the canonical events.
  • T1456 — Add vault existence/structure verification at session start.
  • Companion resolver: g-hk-vault-resolve.py (resolves/creates the working vault path; this hook verifies the configured path independently).

Source: g-hk-vault-verify.py


g-hk-wpac-inbox-check

What it does:

Python port of g-hk-wpac-inbox-check.ps1 (T1584). Cross-project INBOX scanner (T168 rewrite). Safe to call at session start, before command work, during swarm heartbeats, and at final summaries. Reads .gald3r/linking/INBOX.md, surfaces a per-item one-line summary grouped by type, and auto-actions LO

Source: g-hk-wpac-inbox-check.py


raw-inbox-watcher

Fires On:

The canonical stop event with --hook-mode (T1627, WS-A-4). Wired in g_hk_core.py CONCERN_CHAIN["stop"] and registered directly on the Claude Code (.claude/settings.json hooks.Stop) and Cursor (.cursor/hooks.json stop) triggers — deliberately BEFORE g-hk-vault-reindex.py, so routed files land in the same index regen. In hook mode the exit code is always 0 (a lifecycle hook must never block the host session); failures are still moved to raw/failed/ and flagged. Also invocable manually via @g-vault-process-inbox or directly: python .claude/hooks/raw-inbox-watcher.py. No FileSystemWatcher service is installed — Phase 3 will add a watcher daemon.

What It Does:

Scans {vault}/raw/ for dropped files, classifies each by extension and content (rules-based — no LLM in Phase 2), and routes accepted files to the appropriate vault destination via existing g-skl-ingest-* skills. Re-running on an empty raw/ is a no-op (which keeps the stop-chain registration cheap). Supports -DryRun for inspection-only mode and -VaultPathOverride for non-default vault locations.

Side Effects:

  • Moves accepted files from {vault}/raw/ to {vault}/raw/processed/YYYY-MM-DD/.
  • Moves rejected files to {vault}/raw/failed/ and writes an error.md sibling explaining the failure.
  • Appends a run-summary block to {vault}/log.md.
  • Triggers downstream ingest skills (which may write vault notes, update _index.yaml, etc.).

Related Tasks:

  • T1627 (WS-A-4) — registered the watcher on the canonical stop event.
  • Vault subsystem — raw inbox processing (Phase 2)
  • g-skl-vault raw ingestion lifecycle

Source: raw-inbox-watcher.py


Last updated: 2026-07-06 (gald3r v2.4.0)

Clone this wiki locally