-
Notifications
You must be signed in to change notification settings - Fork 4
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
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
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/, whichgald3r worktree cleanupalready 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 orphanedclaude/cursorprocesses 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
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 invokesg-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.login 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
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
Fires On:
-
The canonical
stopevent (T1624, WS-A-1) — wired ing_hk_core.pyCONCERN_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 onehookactivation 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
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
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_countand emit a re-invoke decision (decision:blockfor Claude /continue:false+followupfor 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.jsonon 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-awarethrottle). 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-awareflag for Fix Direction #3, and the re-invoke contract).
Source: g-hk-ggo-stop-detect.py
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
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
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
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
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
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-endcanonical entrypoint. - Shared core:
g_hk_core.py(dispatch,CANONICAL_EVENTS).
Source: g-hk-on-tool-end.py
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
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-submitcanonical entrypoint. - Shared core:
g_hk_core.py(dispatch,CANONICAL_EVENTS).
Source: g-hk-on-user-prompt-submit.py
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
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=...(orsession-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_sessionevent 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
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_skillreference 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
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
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
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_sessionevent 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
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_skillreference 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
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 splicesadditional_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
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
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
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
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
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
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
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
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.-Forcealways 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(-ForceRunbypasses). 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
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*.mdnotes 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.mdname). -
Debounce: skips the regen when both artifacts exist, the recorded
note count matches, no legacy
_INDEX.mdMOC views await unification, and no note is newer than_index.yaml— rapid successive Stops do not trigger redundant regens.-ForceRunbypasses. - 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,# Sectionheadings,* [Title](relative-url) - one-line descriptionbullets.# Sectionslinks down to child-directory indexes (progressive disclosure),# Noteslists the directory's own notes, and the root adds# Recent Updates.index.mdis a derived view and never replaces_index.yaml. - Deletes stale generated per-directory
index.mdviews (identified by theirAuto-generated by g-hk-vault-reindexmarker line) from directories that no longer hold notes, plus legacy per-subdir_INDEX.mdMOC views left behind by the retiredgen_vault_moc.pyscript (identified by theirgen_vault_moc/auto_generated: truemarkers) — their per-subdir index function is unified into this ONE generator'sindex.mdviews (T1632 / WS-B-9). Hand-writtenindex.mdand_INDEX.mdfiles 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.mdMOC 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
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.envfallbacks (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 signalg-hk-vault-migrate --if-divergedgates 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 -psemantics). - 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
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
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
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)