Wiki documentation — Gald3r-Labs/gald3r
gald3r Hooks — Reference
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:
- 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).
- 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.
- Rescues dirty worktrees first: any uncommitted changes are committed to the
worktree's own branch before removal — never force-discarded.
- Unlocks + force-removes the worktree directory, keeping the branch ref.
- Deletes the branch only if fully merged into
main; unmerged branches are kept
for triage.
- 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.logon 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 janitorverb 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:
g-hk-agent-completereads the stop payload, resolvestranscript_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>] ....
- 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_syscomponent 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:
- The canonical
stopevent (T1624, WS-A-1) — wired ing_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.
- 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_STATSis 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
-PreCommitmode, re-stages normalized files withgit addso the fix lands
in the commit.
- In
-Scanmode, 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
.ps1BOM 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/hooks → cursor, .claude/hooks → claude) 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:
- No marker / not active → allow exit (no-op).
- 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).
- Session mismatch (
stored.session_id≠ current session) → allow exit. A
fresh chat session is never forced to resume a prior session's run.
authorized_hard_stoppopulated → a genuine hard-stop row was recorded;
allow exit and clear the marker. Genuine hard stops are NEVER re-invoked.
budget_remaining <= 0→ budget cap is itself a hard stop; allow exit.reinvoke_count >= min(budget_remaining, 25)→ anti-infinite-loop fail-safe;
allow exit.
- 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_countandupdated_atin.gald3r/logs/ggo_run_state.json
on each re-invoke (case 6).
- Writes
session_id(and backfillsplatformif 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_sessionexample). - 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_*.jsonmarkers 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_sessionexample). - 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
PreToolUsehook 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.mdfieldpre_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-homebreadcrumb next to a migrated legacy file. - Never touches project state (
.gald3r/), never blocks anything.
Related Tasks:
- T627 — reconcile onto the unified home (retired the
~/.gald3ridentity 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:
- Consults the
g-hk-vault-resolve.pysibling for source/destination
defaults and the divergence signal.
- Merges
log.mdfiles block-wise (##headings, dedup, destination
blocks first).
- 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.
- Triggers
g-hk-vault-reindex.pyfor 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.mdviews. - 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
.ps1hook. - T1600 — removed the
.ps1fallback 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:
- 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).
- 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.
- 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.
- Writes an OKF-style
index.mdper 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.
- Deletes stale generated per-directory
index.mdviews (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.yamland the per-directoryindex.mdviews. - Removes marker-carrying stale
index.mdviews 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.mdunification;
personal-vault rerun consuming this fixed generator.
- T1634 (WS-B-11) — vault lint that validates the
index.mdformat. - T1584 — Python port of the original
.ps1hook.
_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:
- Reads
vault_location/repos_locationfrom.gald3r/.identity, with
.env fallbacks (GALD3R_VAULT_LOCATION / GALD3R_KNOWLEDGE_WELL_PATH / GALD3R_REPOS_LOCATION).
- Falls back to the local
.gald3r/vault/and.gald3r/repos/when the
shared location is unset, {LOCAL}, or not writable.
- Flags
VaultMigrationCandidatewhen the local vault holds markdown notes
while a different shared vault is configured — the divergence signal g-hk-vault-migrate --if-diverged gates on.
- Ensures the project's
projects/<name>/sessions|decisionsvault
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
.ps1hook. - 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:
- Reads the configured
vault_locationdirectly from.gald3r/.identity(the
declared value, not a resolved/auto-created path).
- Skips silently when
vault_locationis absent or{LOCAL}(local fallback) —
there is nothing centralized to verify.
- Emits one status line:
Vault at {path}: OK— root exists and allresearch/subdirs present.Vault at {path}: NOT FOUND— configured path does not exist; offers
@g-vault init.
Vault at {path}: PARTIAL (missing: ...)— root exists butresearch/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 anerror.mdsibling 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
stopevent. - Vault subsystem — raw inbox processing (Phase 2)
g-skl-vaultraw ingestion lifecycle
_Source: raw-inbox-watcher.py_
Last updated: 2026-07-06 (gald3r v2.4.0)
