Automatic Neatlogs observability for Codex sessions through lifecycle hooks.
Current release: 0.1.1
Requires Node.js 18 or newer.
npm install -g @neatlogs/codex@latest
neatlogs-codex --versionTo update an existing installation:
npm install -g @neatlogs/codex@latestGlobal setup is recommended because it traces Codex in every project:
neatlogs-codex setup --global --api-key YOUR_PROJECT_KEY
neatlogs-codex status --global
neatlogs-codex doctor --globalThe setup command stores the key in ~/.config/neatlogs/config.json with
user-only permissions and registers all supported lifecycle hooks in
~/.codex/hooks.json. Running the command again updates the key and refreshes
the owned hook entries without removing unrelated hooks.
For only the current repository, run this from its root instead:
neatlogs-codex setup --project --api-key YOUR_PROJECT_KEY
neatlogs-codex doctor --projectThis writes <repo>/.codex/hooks.json; the API key remains in the shared
Neatlogs config file.
Start a new Codex session after setup:
codexOpen /hooks inside Codex and review and trust the Neatlogs hook definitions.
Submit a prompt, wait for the response to finish, then open
Neatlogs Traces. Each main-thread user prompt
is represented as one trace/turn. Subagent work is nested inside its parent turn
instead of creating an additional conversation turn.
Run these in order in a fresh Codex session to exercise the principal mappings.
-
LLM metadata, reasoning, and I/O:
Without using tools, reason carefully about three risks of lifecycle-hook observability and give a concise recommendation. -
Successful tool call and multiple LLM phases:
Use Bash exactly once to run `pwd && git branch --show-current && git rev-parse --short HEAD`, then summarize the result. -
Failed tool call:
Use Bash exactly once to run `sh -c 'echo expected-test-failure >&2; exit 7'`. Do not retry; explain that this was an intentional test. -
Permission lifecycle:
Use Bash to run `curl -I https://example.com`. If network access is sandbox-blocked, request elevated execution exactly once, then report what happened. -
apply_patchlifecycle:Use apply_patch to create neatlogs-smoke-test.txt containing "temporary smoke test", inspect it, then use apply_patch to delete it. Leave no repository changes behind. -
Subagent nesting and turn scoping:
Spawn exactly one subagent. Ask it to inspect package.json, use Bash exactly once to run `pwd`, and report the package name and working directory. Then summarize its result. Do not perform the inspection yourself.
After prompt 6, the Neatlogs session should contain exactly six turns. The last
turn should contain TaskCreated, an AGENT span, nested LLM spans, and the nested
Bash span. Root execution/session fields should remain on the WORKFLOW span;
LLM, TOOL, and AGENT spans contain only their relevant fields.
To exercise compaction, run /compact, then submit another prompt. Exit Codex
normally to exercise SessionEnd. Some values, including cache usage, plaintext
reasoning summaries, and permission events, appear only when Codex emits them.
neatlogs-codex setup [--global|--project] [--api-key KEY]
neatlogs-codex status [--global|--project]
neatlogs-codex doctor [--global|--project]
neatlogs-codex uninstall [--global|--project]
neatlogs-codex --versionneatlogs-codex hook is the hook runtime entry point and is normally invoked
by Codex rather than manually.
neatlogs-codex uninstall --global
npm uninstall -g @neatlogs/codexUse --project instead of --global when the hooks were registered only in the
current repository. Uninstalling hooks does not delete the shared API-key file.
- Run
neatlogs-codex doctor --globaland confirm that an API key and all hook events are registered. - Restart Codex after installing or updating the package.
- Open
/hooksand confirm the Neatlogs definitions are trusted. - If global npm installation reports
EACCES, use a Node version manager or a user-writable npm prefix.
npm install
npm run typecheck
npm test
npm run buildThe generated dist/ directory is part of the plugin artifact because bundled hook commands execute dist/cli.js directly through ${PLUGIN_ROOT}.
The current runtime:
- maps documented Codex session, turn, tool, permission, compaction, subagent, stop, and interrupt hooks;
- keeps exactly one trace per main-thread user turn; subagent hook streams never become sibling conversation turns;
- defensively enriches LLM spans from Codex's transcript with per-request input, output, cached, cache-write, and reasoning tokens;
- records reasoning effort, available reasoning summaries, tool calls, duration, and time to first token without exporting encrypted reasoning content;
- emits Claude-compatible derived telemetry for tool failures and subagent task lifecycle events;
- derives deterministic trace, root, tool, and agent IDs;
- exports protobuf OTLP traces to Neatlogs;
- keeps retryable batches in a restrictive local spool;
- redacts common secret-bearing keys from captured tool input and output;
- never writes hook diagnostics or protocol output to stdout.
Codex has 12 native hook events, including Interrupt. Unsupported Claude event names are not placed in
hooks.json; Codex would never emit them. Instead, the runtime maps supported
equivalents onto the same Neatlogs semantic attributes used by the Claude Code
package and exports the compatibility result from the package API.
| Claude Code event | Documented Codex source | Coverage |
|---|---|---|
PostToolUseFailure |
PostToolUse |
Derived when the response reports failure |
TaskCreated |
SubagentStart |
Derived task-lifecycle equivalent |
TaskCompleted |
SubagentStop |
Derived task-lifecycle equivalent |
PermissionDenied |
PostToolUse |
Conditional; only with explicit approval-denial evidence |
StopFailure |
None | Unavailable; SessionEnd also represents normal endings |
InstructionsLoaded |
None | Unavailable; loaded instruction files are not disclosed |
Derived telemetry uses established fields such as neatlogs.span.kind,
neatlogs.workflow_name, neatlogs.task_id, neatlogs.tool.*,
neatlogs.agent.*, neatlogs.input.value, neatlogs.output.value, and
error.message. It does not emit compatibility-only neatlogs.codex.*
attributes that the platform's simplified view would discard. The exported
CODEX_COMPATIBILITY_EVENT_SUPPORT manifest still includes all six names,
including the two that cannot be inferred safely.
Codex hooks directly expose the active model and a transcript path. Token usage,
reasoning effort, timing, and reasoning summaries are read defensively from that
turn's transcript and emitted on individual LLM spans. The parser is bounded,
tolerates malformed or partially flushed JSONL records, and falls back to the
documented last_assistant_message hook field when enrichment is unavailable.
Standard LLM fields use the platform's existing neatlogs.llm.* attributes:
model, provider, effort, prompt/completion/total/reasoning tokens, cache-read and
cache-write tokens, duration, time to first token, tool calls, thinking summary,
and has_thinking. Subagent transcripts receive the same enrichment and their
LLM and tool spans are parented to the corresponding agent span inside the
main-thread turn trace. Codex subagent hook streams reuse the parent session ID,
so the handler identifies their separate transcript ownership and leaves turn
creation to the parent thread's UserPromptSubmit/Stop pair.
Additional runtime context is sent through the backend-supported
neatlogs.workflow.* and neatlogs.session.* namespaces. Root WORKFLOW spans
receive cwd, permission and approval modes, collaboration and sandbox modes,
network access, turn status/error, session source/client, CLI version,
originator, git branch/commit, and session-end reason. LLM spans receive the
runtime values relevant to the model request, including effort, model context
window, and time to first token. The backend strips the leading neatlogs. and
stores these custom fields in spans_simplified.span_metadata.
Codex stores raw reasoning as encrypted content. This package never exports that
encrypted value; it exports only a plaintext reasoning summary when Codex makes
one available, plus the reasoning-token count and has_thinking indicator. It
also does not export transcript paths, agent transcript paths, base instructions,
workspace-root lists, writable-root lists, repository URLs, or account/rate-limit
data.
Codex documents the transcript format as unstable. Missing or changed transcript fields therefore degrade enrichment only; documented lifecycle-hook tracing still continues.
The npm, package-lock, and Codex plugin versions must stay aligned. The package
uses public access for the @neatlogs scope and reruns typecheck, tests, and the
production build during npm publish:
npm login
npm whoami
npm publishnpm may also request the account's two-factor authentication code.