@ascenda-one/codex-hooks
v0.1.35
Published
OpenAI Codex hooks adapter for Ascenda AI workload telemetry
Readme
Ascenda Codex Hooks
OpenAI Codex hooks adapter for Ascenda AI workload telemetry.
Part of ai-engineer-tools. Event mapping: docs/CODEX_MAPPING.md. Codex hooks reference: developers.openai.com/codex/hooks.
What it sends
Metadata only. It doesn't send prompts, responses, code, file names, repository names, branch names or terminal output. Privacy defaults has the detail.
It records the same signals as the Claude Code hooks, from Codex sessions:
| Signal | What the hook records |
| --- | --- |
| Agent interaction | That a prompt, correction loop or compaction happened, and for each tool call the tool's name (shell, apply_patch) |
| Long agent runs | Session starts, and that a turn ran long (agent_loop_long, measured locally) |
| Friction | Tool failures and context pressure |
| Verification | A test, lint or build command ran (editor_verification_activity / compile_error) |
Identity: Codex rides the canonical cli_agent toolType/source (the backend registry has no codex value yet); events carry metadata.host: "codex" for later disaggregation.
Hook safety contract
Codex awaits command hooks and treats exit code 2 as blocking the user's action. This adapter therefore always exits 0 and caps every HTTP call at 3 s (ASCENDA_HTTP_TIMEOUT_MS to change): telemetry failures surface as a one-line systemMessage or stderr note and never stall or block the engineer.
Install
Prerequisites
- Codex CLI with hooks support (v0.117+)
- Node.js 20+
1. Set up
One command pairs Codex, installs the hook binary, and registers the hooks:
npx -y @ascenda-one/codex-hooks setupIt prints a 6-digit code — confirm it in the Ascenda app under Connections → Ingest telemetry — then writes:
- the write token to
~/.ascenda/tokens/ - the pairing to
tools.codexin~/.ascenda/credentials.json - the hook entries to
~/.codex/hooks.json
There is no shell profile line to add. Codex reads its identity from the credentials file, which is per agent — so pairing Codex cannot disturb a Claude Code or Cursor pairing on the same machine, and a Codex hook launched from the Dock with an empty environment still names itself correctly.
Hooks register machine-wide by default. --scope project puts them in
<cwd>/.codex/hooks.json instead, for that project only. Each hook runs
through a small launcher at ~/.ascenda/bin/ascenda-codex-hook that finds
Node 20 or newer itself, so hooks keep working when Codex starts without
your shell's PATH. --no-pair installs without pairing: the screen saver
and live view work, and nothing is sent.
2. Check it
npx @ascenda-one/codex-hooks status
~/.ascenda/bin/ascenda-codex-hook doctorstatus names the pairing, the Node the hooks will run, the last hook Codex
ran and how many events are registered. It exits non-zero when something is
missing, so it can gate a CI step. doctor also checks the live signal, and
pings the Waterline screen saver when it is running.
To undo everything it wrote:
npx @ascenda-one/codex-hooks uninstall3. Restart Codex and review hooks
Open /hooks in the Codex CLI and review and trust the Ascenda commands.
Codex skips untrusted hooks; registration and workspace trust alone do not
authorize them. Changes to a hook definition require review again. See the
Codex hook trust reference.
The setup command registers hooks but does not write Codex trust hashes or
bypass review. A successful status checks installation, not runtime trust
or accepted delivery.
On a Dev backend with no phone
setup --local points at the dev server, which
auto-confirms the pairing:
node ../ascenda-dev-server/dist/cli.js # in another terminal
npx @ascenda-one/codex-hooks setup --local --scope user--api-base-url takes any other host; the default is https://api.ascenda.one.
Registering hooks by hand
setup is the supported path. examples/hooks.json
is the same content for anyone who would rather merge it themselves, or who
needs the inline config.toml form:
[[hooks.UserPromptSubmit]]
[[hooks.UserPromptSubmit.hooks]]
type = "command"
command = "npx -y @ascenda-one/codex-hooks UserPromptSubmit"
timeout = 10A hand-merged file still needs a pairing — run setup for that, or export
ASCENDA_TOOL_INSTALLATION_ID yourself. A matching cli_agent: id or an unqualified id overrides the credentials
file. An id qualified for another tool type, such as claude_code:, is ignored
so a shell-wide setting cannot redirect Codex events to that pairing. Multiple
agents using cli_agent should use per-host credentials instead of a shared
environment override.
Verify
Run a Codex session, then read the send journal at
~/.ascenda/state/<installationId>.json. Look for lastOutcome: "accepted" and a fresh attempt timestamp. The journal
records delivery attempts under the resolved installation id, not hook process
starts. An absent expected journal can also mean a different identity was
selected, input processing failed before delivery, or the hook mapped to no
event. Check the local event log for metadata.host: "codex" and confirm its
toolInstallationId matches tools.codex in the credentials file. The hook
always exits 0, so its exit code alone cannot prove delivery.
An unpaired install logs every payload to ~/.ascenda/events.jsonl. On a
paired one, setup --event-log or ASCENDA_EVENT_LOG_FILE turns the same log
on, and off turns it off.
Build from source
Not needed to use this — npm ships it prebuilt. It is here so you can verify what runs.
# from the repo root (workspace install + shared packages first)
npm install
npm run build:shared
cd ascenda-codex-hooks
npm run build
npm link # exposes `ascenda-codex-hook`
which ascenda-codex-hook
npm run test # unit tests for the mapping
npm run test:sample # pipes a sample PostToolUse payload through the CLIA local build exposes the binary as ascenda-codex-hook — substitute it for
npx -y @ascenda-one/codex-hooks in the hook config above. If it is not on
Codex's PATH, use the absolute path to the binary.
Supported Codex hook events
SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, PostCompact, Stop, PermissionRequest. (SubagentStart and SubagentStop have no catalog counterpart and are skipped.)
PermissionRequest records that the agent stopped at an approval gate, and
nothing about what it was asking to do — one field, interruptionKind:
"permission_request". The command, its arguments and the paths involved are
never read.
Turn duration
Codex's Stop payload has no duration, so the adapter records a turn-start timestamp per session under ~/.ascenda/state/ at UserPromptSubmit and consumes it at Stop; turns of 30+ minutes emit agent_loop_long, and every turn emits ai_turn_completed. State failures degrade silently to "no duration".
Privacy defaults
Metadata-only. Does not send prompts, responses, code, file names, repository names, branch names, or terminal output. Correction detection runs locally on prompt text; only the classification is transmitted. consentScope: ide_telemetry, provenance: ai_work_telemetry on every event.
Repository and branch identity travel only as machine-salted digests, never as
text: workspaceHash/projectHash from folder names, and metadata.branchHash
from the branch the checkout is on. A branch name is low-entropy, so that digest
is not a security boundary — the machine salt is what makes the input space
unguessable. On a detached HEAD, outside a checkout, or with no readable salt
the key is omitted rather than sent blank.
