Skip to content

Host Hooks

github-actions[bot] edited this page Oct 1, 2026 · 3 revisions

Session hook adapters

Project registrations use a prepared checkout or explicitly selected local CLI. Installed plugin hooks use a dependency-free runtime and explicit host manifests; the two surfaces have different launch and state selection rules.

The unified CLI renders available-skill context and adapts its output for the selected host. Generation prints JSON only; it never writes settings, enables hooks, installs dependencies, or reads incoming prompts/transcripts. Antigravity/Hermes execution parses bounded lifecycle stdin to gate the first invocation; it discards all unrelated fields and never opens a transcript. Prepare Node 24 and the selected runtime first; checkout execution requires explicit npm ci. Session events never install dependencies.

Host Proposed project location Event Output Timeout unit
Codex .codex/hooks.json SessionStart Plain context Seconds
Claude Code .claude/settings.json SessionStart hookSpecificOutput with hookEventName and additionalContext Seconds
GitHub Copilot CLI .github/hooks/i9-skills.json sessionStart additionalContext Seconds (timeoutSec)
Gemini CLI .gemini/settings.json SessionStart hookSpecificOutput.additionalContext Milliseconds
Antigravity .agents/hooks.json PreInvocation with invocationNum=0 injectSteps containing ephemeralMessage Seconds
Hermes profile config.yaml pre_llm_call with extra.is_first_turn=true context Seconds
OpenCode Plugin-specific Not implemented Use the manual context command Not applicable

Generate with node bin/index.mjs hook session-config --host HOST. Review the result in a caller-owned scratch file and compare it using node bin/index.mjs hook verify --host HOST --file FILE. Verification compares the whole standalone generated object and rejects malformed/linked input. It does not merge existing settings or certify unrelated settings. If installation is later authorized, merge the reviewed hook entry while preserving other host configuration. Remove that entry to disable the adapter.

Without selection flags, generated commands target a POSIX Git checkout containing this CLI. Git resolves its root even when the host starts in a nested directory. Windows, remote cloud sandboxes and other shell formats need separately tested invocation adapters. A missing Node executable, dependency or trusted checkout is a setup failure, never a reason to fetch software at session startup.

For an already installed project-local or global CLI, select its absolute executable with --executable. The same selections must be supplied to verify:

i9-skills hook session-config --host claude \
  --executable '/absolute/consumer/node_modules/.bin/i9-skills' \
  --project '/absolute/consumer' --no-global > /absolute/scratch/session-hooks.json

i9-skills hook verify --host claude --file /absolute/scratch/session-hooks.json \
  --executable '/absolute/consumer/node_modules/.bin/i9-skills' \
  --project '/absolute/consumer' --no-global

No global installation is required: an existing local executable or retained npm cache executable can be selected. Package-manager links resolve to their actual runtime file. Missing, directory, relative or non-executable selections fail generation and verification. Quoted paths, including spaces and apostrophes, remain literal. These checks establish current file access only; they do not prove executable contents, dependencies, host trust or future availability. A removed cache needs explicit runtime replacement and configuration regeneration.

Installed commands always pass a consumer project explicitly. --project embeds its normalized absolute root; without it, the command uses the hook's working directory at execution via "$PWD". It does not use the installed toolkit as the fallback project or capture the generator's working directory. When a host starts in a nested directory or a separate launcher directory, select the actual consumer root with --project. Global metadata remains enabled by default through shared agent state; --no-global selects only the project, --global-root embeds another selected global directory, and --max-entries 1..100 sets the displayed package limit with omissions disclosed. Generation and verification accept these same flags, compare the complete standalone object and write no settings or evidence. Existing discovery and filesystem confinement rules still apply.

Claude's matcher selects startup, resume, clear and compact. Gemini's lifecycle matcher is an exact value, so its adapter leaves the matcher absent to cover documented SessionStart events rather than sending a pipe-delimited regex. Copilot uses its documented camelCase contract. Codex's current official contract supports the local registration, source matcher, plain developer-context stdout, seconds timeout, status text and additionalContextLimit. The configured status text remains "Loading available skills overview". Its context budget is a host output limit, not a claim that every discovered package was loaded.

Antigravity maps a hook name directly to event arrays; its invocation handler is a direct list, without a tool matcher. Hermes configuration is normally YAML; generation prints the equivalent standalone JSON object for review and merging. Hermes tokenizes its command without a shell, so this adapter explicitly invokes sh -c for the selected POSIX command, including literal installed paths. Neither adapter grants tool permissions. Both skip subsequent invocations, read at most 1 MiB of strict JSON and retain none of the input. Missing lifecycle metadata fails instead of claiming startup.

Run node bin/index.mjs hook session-index --host HOST --project PATH --no-global to inspect the corresponding output without installing a hook. For Antigravity, provide {"invocationNum":0} on stdin; for Hermes provide {"hook_event_name":"pre_llm_call","extra":{"is_first_turn":true}}. Omit --no-global only when global metadata discovery is intended. Output contains bounded package summaries, never a claim of activation. Metadata and descriptions are untrusted shortlist data; selecting a package still requires its entrypoint and host capabilities. Unsupported hosts use context available-skills.

Availability and routing guidance

The overview calls a package available only when its metadata was readable and parsed. It does not verify setup, lifecycle status, host usability or activation. Because session startup supplies no task or routing decision, Selected route is unassessed. A none route is a later decision that no skill fits or is needed; it must not be inferred simply because no task was supplied.

Any discovered skill-routing package is listed as an unverified candidate, not trusted because of its name. Colliding names retain their separate identities. After inspecting the actual package, choose a single owner, a sequence with distinct ordered outputs, an ambiguous shortlist of at most three candidates requiring more input, or none. This is guidance, not automatic routing.

Each displayed summary includes a JSON-quoted SKILL.md locator. Decode its escapes before reading; do not treat a displayed filename as a shell command. Bundled catalog locations are labeled collection-relative. Locators exceeding 512 escaped characters are omitted whole, with instructions to inspect the selected collection. Read only the selected entrypoint and needed references. Package/context limits and omitted counts still apply; a truncated overview does not establish complete availability.

Evidence and limitations

Reviewed on 2026-09-19: Codex hooks, Claude hooks, Copilot hooks, and Gemini hooks, plus Antigravity hooks and Hermes shell hooks. Codex CLI 0.155.1 was inspected locally for version/help; it was not started as an agent session. The official Codex reference confirms that project hooks run with the session working directory, so the launcher uses the Git root. Codex combines matching hooks across sources; copying the entry to another layer can produce duplicate context. No source or trust setting was changed here. These changing interfaces require rechecking before installation. Tests cover configuration, timeout units, context envelopes, mismatch failures and no configuration writes in disposable fixtures. Native host trust, enablement and execution have not been exercised. No cross-host runtime certification is made.

The installed CLI selection in issue #54 was checked against the same official references on 2026-10-01. Disposable local executable fixtures exercise all six generated commands in a non-Git consumer, including quoted paths, explicit discovery selections and absent runtimes. These process tests are distinct from native host delivery and the historical plugin pilot. Host timeout units, context envelopes and opt-in trust remain unchanged; retain Node 24 and the selected executable for every session.

OpenCode requires a plugin contract rather than these registrations. The official V2 migration guide replaces V1 lifecycle callbacks with scoped session/tool hooks; the two APIs are not interchangeable. No native version, SDK dependency or plugin installation was selected and tested in this delivery. Use context available-skills manually until a versioned plugin adapter is prepared and reviewed.

Native read observation adapters

hook telemetry-config --host HOST --collection project=/absolute/project/.agents/skills prints a reviewed host registration without writing settings. Supported telemetry hosts are codex, claude, gemini and copilot; repeat --collection for explicitly selected roots and use --db to override shared state. The generated command invokes hook observe with those same selections. Install only after reviewing the selected host's configuration and obtaining authority for that environment. This command itself creates no files.

Host Attempt and observation Evidence and limits
Codex CLI 0.159.2 Bash PreToolUse / PostToolUse Literal cat or sed -n 'START,ENDp' on one discovered SKILL.md; exact returned text must match the current bounded selection
Claude Code Read PreToolUse / PostToolUse Successful native Read result field; partial reads count one entrypoint observation
Gemini CLI read_file BeforeTool / AfterTool file_path, native ISO timestamp, result with llmContent and no error
Copilot CLI view preToolUse / postToolUse toolArgs.path, epoch-millisecond timestamp, successful toolResult with text

Codex's official hook reference documents Bash command input and PostToolUse result values. The reviewed immutable 0.159.2 tool context implementation returns raw text for a completed Bash invocation, without an exit-code object. The adapter first identifies the entrypoint inside the selected collections, then opens and compares its bounded UTF-8 bytes. It supports an optional rtk proxy prefix, literal shell quoting, cat -- FILE and numeric sed ranges. It never executes the command. Pipelines, redirection, expansion, multiple files, compound commands, other programs, truncated/mismatching output and object-shaped responses remain unobserved. Empty selections do not count. PostToolUse proves only the returned selection at receipt time; it does not prove full-file reading, comprehension, arbitrary shell success or a historical revision.

The Claude hooks contract supplies native tool-use IDs and successful Read events. Codex and Claude map these session/call IDs to opaque deterministic UUIDv8 identifiers: attempt and observation share correlation, retain distinct event IDs and deduplicate retries. Because these payloads lack an occurrence timestamp, the first committed receipt time is retained. Startup creates a session observation; Claude also counts clear. Resume, compact and fork may supply context without inventing a new start.

The Gemini hook reference and file-system tool reference define its native read_file input, error/result envelope and ISO timestamp. The Copilot hook reference defines the distinct CLI camelCase input and successful view result envelope. These adapters use bounded native timestamps and opaque session IDs. Their documented hooks lack a native tool-call ID: attempts and successful observations therefore have separate timestamp/event/path-derived identities and cannot be paired reliably. Retries with the same receipt deduplicate; two independent identical same-timestamp receipts can collapse. Counts do not conceal that coverage gap. Copilot snake_case/VS Code payloads and legacy payloads missing session IDs are unsupported rather than relabeled as Claude. Gemini accepts UTC ISO timestamps with seconds or milliseconds; unsupported timestamp precision or timezone shapes are ignored with a neutral diagnostic.

Each host preserves revision unknown. The currently discovered package cannot prove a past call's revision. Missing/removed/unrecognized packages, implicit loading, references, failures and other tools reduce coverage. Multiple labels for one canonical package select the lexicographically first label. The CLI requires complete selected-collection discovery; the dependency-free plugin may identify a safe supported entrypoint despite warnings in unrelated foreign metadata, while reporting incomplete discovery.

Raw hook input is strict JSON bounded at 1 MiB and discarded. Storage contains only the typed metadata envelope: no prompts, bodies, paths, commands, transcripts or original native IDs. Codex observation output is empty; the other hosts receive {}. Invalid payloads, failed storage and unsupported events remain nonblocking, with fixed redacted diagnostics where applicable. No permission decisions or result modifications are emitted. Fixture tests prove mapping/storage behavior, not live native delivery, enablement or complete measurement.

Installed plugin hooks

Root manifests explicitly select hooks/codex.json, hooks/claude.json or hooks/copilot.json for their respective hosts. The Gemini registration hooks/gemini.json is an example for a separately reviewed extension/settings integration; this repository does not declare a Gemini extension manifest. All registrations invoke src/transport/PluginHookRunner.ts --host HOST directly with Node 24. No Git lookup, build, installed CLI, node_modules or automatic setup runs at event time. Copilot's registration supplies --event because its CLI camelCase input does not include the native event name. Portable skills remain independent of these integrations.

The runner requires a bounded absolute existing event cwd and discovers the caller's .agents/skills, the selected agent state's skills/ and the installed plugin's own .agents/skills. It never rewrites catalogs. Duplicate real package paths merge while equal names at distinct paths remain separate. Context is capped at 24 entries and 4096 UTF-16 characters with complete rows and coverage information; serialized output stays within 16 KiB. Each host receives its own documented context envelope. Session context is retained when telemetry storage is unavailable.

All adapters use the shared default ~/.agents/skills-usage.db even when a host automatically supplies a plugin DATA variable. I9_AGENT_STATE_ROOT selects another root; I9_SKILLS_USAGE_DB explicitly preserves any legacy filename. See shared state and legacy preservation. A valid observation may create only its selected external data directories. Linked ancestors, database/sidecar links, overlap with the plugin/caller, unwritable state and invalid schemas are rejected without a reset fallback. Read-only queries never create or migrate storage. Path inspection assumes a stable owned workspace and does not promise race-proof confinement.

Missing Node is a host launch failure, so prepare Node 24 explicitly or use the prepared CLI manually. Hooks remain subject to the native host's trust and enablement controls. Avoid duplicate enabled registrations across plugin and project layers. There is deliberately no ambiguous hooks/hooks.json default that could dispatch one host's payload to another adapter.

For a diagnostic from disposable owned directories, explicitly select test state:

printf '%s\n' '{"hook_event_name":"SessionStart","source":"startup","session_id":"smoke-1","cwd":"/absolute/test-consumer"}' \
  | I9_AGENT_STATE_ROOT=/absolute/test-state node /absolute/plugin/src/transport/PluginHookRunner.ts --host codex

printf '%s\n' '{"hook_event_name":"SessionStart","source":"startup","session_id":"smoke-1","cwd":"/absolute/test-consumer"}' \
  | I9_SKILLS_USAGE_DB=/absolute/test-data/usage.db node /absolute/plugin/src/transport/PluginHookRunner.ts --host claude

These calls test runtime envelopes and storage, not native hook delivery. Synthetic tests launch configurations, exact read payloads, retries, malformed timestamps, unsupported commands and hostile installed-root spellings under disposable HOME. The historical native pilot has its own named revisions and limits; it does not certify these new mappings. Disable only the selected registration to roll back, preserving every database. Reviewed official references above were checked on 2026-10-01; recheck changing host interfaces before installation.

Clone this wiki locally