> For the complete documentation index, see [llms.txt](https://faction-os.gitbook.io/faction-os-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/sessions/phase09-session02-hook-runtime-normalization/spec.md).

# Session Specification

**Session ID**: `phase09-session02-hook-runtime-normalization` **Phase**: 09 - Codex Provider-Neutral Foundation **Status**: Complete **Validated**: 2026-05-31 **Created**: 2026-05-31 **Package**: apps/hooks **Package Stack**: JavaScript hook handlers, Node process fixtures, Vitest node tests, Biome checks

***

## 1. Session Overview

This session normalizes the hook runtime so existing Claude Code payloads and Codex-shaped payloads can move through the same safe helper layer before handlers post events, write spool entries, or log diagnostics. It follows the Phase 09 protocol baseline, where `codex-cli`, provider-neutral hook names, Codex lifecycle names, and compact optional ingest metadata already exist in `packages/protocol`.

The work stays inside `apps/hooks`. The goal is not to install Codex hooks or add a Codex hook map yet. Instead, this session gives current handlers shared helpers for provider detection, event names, session ids, tool names, tool inputs, model ids, permission modes, prompt text, and subagent metadata, then updates the existing handlers to consume those helpers without changing Claude behavior.

Privacy and local-first behavior are part of the runtime contract. Hook handlers must remain silent, timeout-bound, dependency-light, and credential-free. Server-down paths must keep writing sanitized spool entries, and logs must keep exposing compact counts, ids, labels, durations, and statuses only. Raw prompts beyond approved bounded fields, transcript paths, patch bodies, command output, MCP argument bodies, token-like values, provider credentials, and broad absolute paths must not leak into diagnostics or spool data.

***

## 2. Objectives

1. Add shared provider normalization helpers in `apps/hooks/src/_lib.js`.
2. Update existing handlers to consume normalized fields while preserving Claude hook map behavior.
3. Add Codex-shaped fixture coverage for lifecycle, prompt, Bash, permission, and subagent metadata paths.
4. Harden log and spool sanitization assertions for Codex-specific sensitive fields.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase09-session01-contracts-and-naming-baseline` - provides `codex-cli`, provider-neutral hook event names, Codex lifecycle names, and compact optional ingest fields.
* [x] `phase08-session08-release-candidate-validation-and-documentation-closeout` - provides release claim discipline and residual no-claim wording.
* [x] `phase03-session06-cli-diagnostics-and-recovery-controls` - provides current hook/listener diagnostic and local recovery posture.
* [x] `phase01-session02-hook-runtime-safety` - provides the silent, timeout-bound hook runtime baseline.

### Required Tools/Knowledge

* Node 20+ and npm workspace dependencies.
* Current `apps/hooks/src/_lib.js` helpers for POST, spool, logs, TTY/session state, and subagent lineage.
* Current hook handler payload shapes under `apps/hooks/src/factionos-*.js`.
* Current hook tests in `apps/hooks/tests/hookPayloads.test.js`, `hookRuntime.test.js`, and `orchestrationDiagnostics.test.js`.
* Phase 09 Codex constraints for hook input fields, trust behavior, and no custom slash command claims.

### Environment Requirements

* No Codex, Claude, OpenAI, Anthropic, Cloudflare, hosted, or provider credentials are required.
* Focused tests must use isolated temporary FactionOS homes.
* Hook process tests must keep normal stdout and stderr empty except the existing restricted git-guard denial path.

***

## 4. Scope

### In Scope (MVP)

* Hook maintainers can normalize provider inputs - add `detectCli()`, `hookEventName(event)`, `toolName(event)`, `toolInput(event)`, `modelId(event)`, `permissionMode(event)`, `promptText(event)`, and `subagentMetadata(event)` helpers in `apps/hooks/src/_lib.js`.
* Existing handlers can consume normalized fields - update lifecycle, prompt, stop, Bash, file, permission, input, and subagent handlers to use the shared helpers.
* Claude behavior remains unchanged - keep `apps/hooks/hooks.json`, installed command strings, event names, payload intent, silent failure, and timeout behavior stable.
* Codex-shaped fixtures are accepted - add tests for Codex `session_id`, `hook_event_name`, `model`, `permission_mode`, `prompt`, `tool_name`, `tool_input`, `tool_response`, `agent_id`, and `agent_type` fields.
* Sanitization covers Codex fields - ensure spool and log output redact or summarize `prompt`, `tool_input`, `tool_response`, `transcript_path`, `agent_transcript_path`, command-like values, token-like values, broad paths, and MCP argument bodies.
* Package docs reflect the provider-neutral runtime boundary - document normalization support without claiming Codex installation or hook map completion.

### Out of Scope (Deferred)

* `hooks.codex.json` and Codex hook installation - *Reason: Phase 09 Session 03 and Phase 10 own hook maps and installer behavior.*
* New Codex-specific handler scripts for `SubagentStart`, `PreCompact`, or `PostCompact` - *Reason: Session 03 owns new handler paths and map coverage.*
* Server ingest changes - *Reason: Phase 10 owns server lifecycle ingest beyond current tolerant `/event` behavior.*
* Web labels, filters, or UI changes - *Reason: Phase 10 owns product integration surfaces.*
* Parsing full transcripts, raw patch bodies, terminal output, or MCP argument bodies - *Reason: this session only accepts compact normalized metadata and bounded previews.*

***

## 5. Technical Approach

### Architecture

Centralize provider-specific field differences in `apps/hooks/src/_lib.js`. Handlers should ask the helper layer for normalized values instead of reading provider keys directly. This keeps later Codex hook map work from duplicating fallback logic across every handler.

The helper layer should stay plain JavaScript and dependency-light. It should accept unknown or malformed external hook input defensively, return compact defaults, and never throw during normal handler operation. Helper names should describe normalized concepts rather than provider-specific branches.

Sanitization must remain boundary-specific. The handler payload may still include bounded product fields such as a prompt preview or command preview when that is the intended event contract, but diagnostics and spool fallback must not preserve raw nested Codex payloads, transcript paths, command output, MCP argument bodies, credentials, tokens, or broad absolute paths. Existing POST timeout and spool fallback behavior should remain the failure path; hook processes should not add blocking retries.

### Design Patterns

* Shared normalization helpers: one helper per normalized concept, used by handlers and tests.
* Defensive external input parsing: tolerate missing, malformed, array, or non-object provider fields.
* Backward-compatible adoption: keep existing Claude field names, event names, and tests passing while adding Codex fallbacks.
* Compact diagnostic summaries: log counts, ids, statuses, and lengths instead of raw payload bodies.
* Negative privacy tests: assert sensitive Codex-shaped fields do not appear in spool or log artifacts.

### Technology Stack

* JavaScript ES modules in `apps/hooks`.
* Node process fixtures for handler behavior.
* Vitest node tests through the root `vitest.config.ts`.
* Biome formatting and linting.

***

## 6. Deliverables

### Files to Create

| File                                         | Purpose                                                                             | Est. Lines |
| -------------------------------------------- | ----------------------------------------------------------------------------------- | ---------- |
| `apps/hooks/tests/hookNormalization.test.js` | Unit coverage for provider normalization helpers and Codex/Claude fallback behavior | \~180      |

### Files to Modify

| File                                                | Changes                                                                         | Est. Lines |
| --------------------------------------------------- | ------------------------------------------------------------------------------- | ---------- |
| `apps/hooks/src/_lib.js`                            | Add normalization helpers and Codex-sensitive sanitization coverage             | \~220      |
| `apps/hooks/src/factionos-hero-spawn.js`            | Use normalized session, cwd, event name, CLI, model, and permission metadata    | \~40       |
| `apps/hooks/src/factionos-hero-active.js`           | Use normalized prompt, session, CLI, cwd, and subagent metadata                 | \~35       |
| `apps/hooks/src/factionos-hero-idle.js`             | Use normalized session, event name, CLI, and summary fields                     | \~25       |
| `apps/hooks/src/factionos-bash-command.js`          | Use normalized tool input, tool name, session, CLI, and subagent metadata       | \~35       |
| `apps/hooks/src/factionos-git-guard.js`             | Use normalized Bash input and preserve restricted denial behavior               | \~30       |
| `apps/hooks/src/factionos-file-access.js`           | Use normalized tool name/input and preserve bounded file observability          | \~45       |
| `apps/hooks/src/factionos-permission-request.js`    | Use normalized permission mode, tool name, tool input, and detail summary       | \~45       |
| `apps/hooks/src/factionos-awaiting-input.js`        | Use normalized question tool input while preserving Claude-only gap wording     | \~25       |
| `apps/hooks/src/factionos-input-received.js`        | Use normalized tool response handling and session fields                        | \~25       |
| `apps/hooks/src/factionos-subagent-spawn.js`        | Use normalized subagent metadata with Codex agent fields and Claude fallback    | \~45       |
| `apps/hooks/src/factionos-subagent-complete.js`     | Use normalized subagent metadata for Codex agent fields and Claude fallback     | \~35       |
| `apps/hooks/tests/hookPayloads.test.js`             | Preserve static Claude expectations and assert helper usage where useful        | \~70       |
| `apps/hooks/tests/hookRuntime.test.js`              | Add Codex-shaped process fixtures and no-regression assertions                  | \~160      |
| `apps/hooks/tests/orchestrationDiagnostics.test.js` | Add Codex sensitive-field diagnostic redaction coverage                         | \~60       |
| `apps/hooks/README_hooks.md`                        | Document provider-neutral runtime normalization and remaining Codex install gap | \~60       |

***

## 7. Success Criteria

### Functional Requirements

* [ ] `detectCli()` returns `codex-cli` when `FACTIONOS_CLI=codex-cli` is set.
* [ ] `hookEventName(event)` reads `hook_event_name`, `eventName`, and `hook`.
* [ ] `resolveSessionId(event, tty)` still reads `session_id`, `sessionId`, and the saved TTY state file.
* [ ] `toolName(event)` reads `tool_name`, `tool`, and supported aliases.
* [ ] `toolInput(event)` reads `tool_input`, returns an object, and safely rejects malformed inputs.
* [ ] `modelId(event)` reads `model`, `modelId`, and provider-specific environment fallbacks.
* [ ] `permissionMode(event)` reads `permission_mode` and returns compact diagnostic metadata.
* [ ] `promptText(event)` reads Codex `prompt` and Claude `user_message` without breaking current prompt behavior.
* [ ] `subagentMetadata(event)` prefers Codex `agent_id` and `agent_type`, then falls back to current Claude transcript-path parsing.
* [ ] Existing Claude hook map command expectations remain unchanged.
* [ ] Existing handler process fixtures stay silent and exit 0 when the server is unavailable.
* [ ] Codex lifecycle, prompt, Bash, minimal permission, and subagent metadata fixtures produce bounded payloads.

### Testing Requirements

* [ ] Helper tests cover Claude and Codex fallback order.
* [ ] Runtime tests cover Codex-shaped handler inputs with isolated temp homes.
* [ ] Sanitization tests assert no raw Codex prompt, tool input, tool response, transcript path, agent transcript path, MCP args, token, or broad path leaks into logs or spool.
* [ ] Focused hooks tests pass.
* [ ] Focused Biome check passes for touched hook files.

### Non-Functional Requirements

* [ ] Hook handlers remain dependency-light and timeout-bound.
* [ ] Normal hook execution remains silent on stdout and stderr.
* [ ] Existing Claude Code behavior is unchanged beyond internal helper usage.
* [ ] No Codex installation, hook trust bypass, hosted transfer, custom slash command, or production-hosted claim is introduced.
* [ ] Logs and spool entries remain local, bounded, and sanitized.

### Quality Gates

* [ ] All files ASCII-encoded.
* [ ] Unix LF line endings.
* [ ] Code follows project conventions.
* [ ] `git diff --check` passes.

***

## 8. Implementation Notes

### Key Considerations

* Keep handler changes mechanical after helpers land. The highest-risk part is changing field access without shifting emitted payload intent.
* Do not mutate `apps/hooks/hooks.json` in this session. The Claude map remains the compatibility default until Session 03 adds `hooks.codex.json`.
* Do not add new long-running work to hook processes. Existing bounded POST and sanitized spool fallback remain the runtime failure model.
* Add Codex-shaped fixtures as local JSON inputs, not live Codex runtime claims.
* Keep docs clear that provider-neutral normalization is a foundation, not Codex installation or end-to-end validation.

### Potential Challenges

* Helper adoption can create subtle Claude payload drift: mitigate with static `hookPayloads.test.js` checks and process-level no-regression tests.
* Sanitization can be too broad and remove intended product previews: mitigate by separating handler payload construction from diagnostic/spool redaction expectations.
* Codex field names may evolve: mitigate by keeping helper functions small, source-backed, and fixture-driven.
* Subagent metadata has provider-specific gaps: mitigate by preserving Claude transcript fallback and using Codex `agent_id` / `agent_type` only when present.
* Permission semantics differ by provider: mitigate by forwarding compact `permissionMode` metadata without making allow/deny decisions.

### Relevant Considerations

* \[P03-apps/cli+apps/hooks] **Lifecycle state is local and file-based**: session aliases, listener PID files, spool, and logs must keep crash recovery and stale-state behavior stable.
* \[P07] **Redaction is boundary-specific**: diagnostics and spool fallback need explicit Codex minimization instead of broad payload forwarding.
* \[P03] **Stable docs are the current contract**: use package README and docs/api references as current behavior, not historical examples.
* \[P08] **Release-candidate evidence is claim-scoped**: fixture coverage is not production-hosted validation, trusted erasure, or full Codex runtime proof.
* \[Security] **P06-S07-ERASURE**: local spool/log cleanup remains operational hygiene only and must not be described as trusted erasure.

### Behavioral Quality Focus

Checklist active: Yes

Top behavioral risks for this session:

* External hook input could be malformed, provider-specific, or missing required fields and should degrade to explicit missing-input statuses instead of throwing.
* Handler POST failures must keep bounded timeout and sanitized spool fallback behavior without duplicate blocking retries.
* Diagnostics and spool fallback could accidentally retain raw Codex `tool_input`, `tool_response`, transcript paths, MCP args, prompts, command output, tokens, or broad paths.
* Permission and subagent metadata could imply enforcement or Codex parity that this session does not implement.

***

## 9. Testing Strategy

### Unit Tests

* Test `detectCli()` with `FACTIONOS_CLI=codex-cli` and existing fallback environments.
* Test `hookEventName()`, `toolName()`, `toolInput()`, `modelId()`, `permissionMode()`, `promptText()`, and `subagentMetadata()` with Claude and Codex-shaped fixtures.
* Test malformed provider fields return safe defaults.
* Test sanitization helpers against Codex-specific nested fields and token-like values.

### Integration Tests

* Run focused hook tests:
  * `npx vitest run apps/hooks/tests/hookNormalization.test.js apps/hooks/tests/hookPayloads.test.js apps/hooks/tests/hookRuntime.test.js apps/hooks/tests/orchestrationDiagnostics.test.js apps/hooks/tests/listenerRuntime.test.js`
* Run focused Biome check for touched hook source, tests, and README paths.
* Run `git diff --check`.

### Manual Testing

* Review generated spool entries from process fixtures for bounded event payloads and no raw Codex-sensitive fields.
* Review `apps/hooks/README_hooks.md` for no claims about Codex installation, hook trust bypass, custom slash commands, hosted transfer, or production validation.
* Review handler payloads to confirm existing Claude event names and product intent are unchanged.

### Edge Cases

* Empty stdin, malformed JSON, arrays, and non-object `tool_input` or `tool_response`.
* Missing `session_id` with a saved TTY session fallback.
* `FACTIONOS_CLI=codex-cli` with Claude-shaped event fields.
* Codex `prompt` present without Claude `user_message`.
* Codex `agent_id` and `agent_type` present without transcript paths.
* MCP tool names or argument bodies embedded under nested tool input fields.
* Permission requests with `permission_mode` but no plan text.
* Server unavailable, malformed server URL, full spool directory, and log write failures.

***

## 10. Dependencies

### External Libraries

* None expected. Use existing Node, Vitest, npm workspace, and Biome tooling.

### Other Sessions

* **Depends on**: `phase09-session01-contracts-and-naming-baseline`
* **Depended by**: `phase09-session03-codex-hook-map-and-codex-specific-handlers`, `phase10-session02-codex-hook-install-uninstall-and-diagnostics`, `phase10-session03-server-ingest-codex-lifecycle`

***

## Next Steps

Run the implement workflow step to begin AI-led implementation.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/sessions/phase09-session02-hook-runtime-normalization/spec.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
