Codex Rules: per-session cache for additionalContext (Claude Code InstructionsLoaded parity)

Open 💬 3 comments Opened May 8, 2026 by oalanicolas

Problem

A common Codex hook pattern: UserPromptSubmit reads project rules
(e.g. .claude/rules/*.md with paths: frontmatter — same convention
Claude Code uses), matches them against paths mentioned in the user's
prompt, and emits matched rule bodies as
hookSpecificOutput.additionalContext.

Problem: Codex re-injects the same rule bodies on every prompt.

If a user works on apps/design/ for 20 turns, the same 5 rules
(~10 KB of additionalContext) get re-injected 20 times. That's
~200 KB of redundant context for a small project — bigger projects
easily exceed 1 MB per session.

How Claude Code solves this natively

Claude Code's InstructionsLoaded event (with load_reason:
nested_traversal | path_glob_match | session_start | compact | include
)
fires only when:

  1. A rule file is discovered for the first time in the session
  2. The model touches a path that triggers a previously-unseen rule
  3. Cache is invalidated (compaction)

A rule loaded on turn 1 stays in context. The hook on turn 2 sees the
same paths but emits nothing new — the rule is already there.

Source: src/utils/attachments.ts:1755-1769 (memoryFilesToAttachments,
where path_glob_match fires) and src/utils/claudemd.ts:1395 (uses
the ignore library for
gitignore-semantics path matching).

Empirical impact (our setup)

We built a userland workaround: per-session cache file at
~/.codex/cache/<plugin>/<session_id>.json, mtime-based invalidation,
gitignore-semantics matching via the ignore library.

| Turn | Action | additionalContext size |
|------|--------|------------------------|
| 1 (cold) | edit apps/design/foo.tsx | 10,225 chars (full rule bodies) |
| 2 (warm) | edit apps/design/bar.tsx | 356 chars (status only: "already in context") |
| 3 (touch + edit) | touch .claude/rules/x.md && edit apps/design/baz.tsx | ~2 KB (re-injects only the changed rule) |

~96.5% context reduction on the warm path. A 20-turn session in the
same domain saves ~200 KB.

Proposed approaches (4 — pick what fits the team's roadmap)

  1. Native event (heaviest): add InstructionsLoaded-equivalent

that hooks can subscribe to with built-in path-glob matching
against frontmatter. Fully reactive (fires on tool path access).
Mirrors Claude Code 1:1.

  1. Helper library (medium): publish a Codex helper that hooks

call to compute "what's new since last turn" given session_id

  • candidate set. No core changes; pattern becomes documented.
  1. Cookbook docs (lightest): publish the pattern as official

guidance in docs/hooks/ — "hooks that inject state should key
cache by session_id and dedupe against prior turns."

  1. Marketplace plugin (zero core changes): ship our reference

implementation as an official curated plugin. Users install once
via codex plugin marketplace add; works for any project with a
rule directory.

Reference implementation (open to contribute)

~530 lines of Node, MIT-able. Behaviors covered:

  • gitignore-semantics matching via ignore lib (Claude Code parity)
  • Brace expansion in paths: frontmatter ({ts,tsx})
  • @-mention extraction (priority candidate)
  • Conversational keyword extraction (top-level dir mentions —

necessary because Codex hooks fire BEFORE tool decisions, so we
can't be purely reactive like Claude Code)

  • Session-scoped JSON cache with mtime invalidation
  • Atomic writes, 30-day TTL pruning, cache failure is non-fatal

Happy to extract a clean version under whichever path the maintainers
prefer (1/2/3/4).

Adjacent issues

  • #16430 (plugin-local hooks not firing in 0.128.0) — currently

blocks distributing this as a plugin; registering globally as
workaround. Confirmed reproducible in 0.128.0 release.

  • #19385 (PreToolUse additionalContext not supported) — would

enable fully reactive loading like Claude Code's path_glob_match.

  • #20766 (TUI: collapse hook context) — UI-side mitigation but

doesn't reduce the token cost.

View original on GitHub ↗

3 Comments

oxysoft · 3 months ago

Indexed this hook ticket in the umbrella tracker: #21753

Goal: collect the scattered Codex hook requests and bugs into one parity matrix for Full Claude Code Hook Parity (29+), while preserving this issue as the detailed thread for its specific behavior.

jt-ana · 3 months ago

+1 for this feature

bengous · 1 month ago

I have a working Rust hook for this use case now: https://github.com/bengous/codex-path-rules

The pattern is simple:

  • scan .claude/rules/*.md
  • parse paths: frontmatter
  • on PreToolUse, extract touched paths from Bash, apply_patch, Edit, Write, and MultiEdit
  • inject matching rule bodies through hookSpecificOutput.additionalContext
  • dedupe per session_id
  • reset on SessionStart, SessionEnd, and PostCompact
  • cap output size so a rule cannot blow up the context window
  • fail open, since this is context injection, not policy enforcement

I have been moving more and more toward lazy, path-triggered context instead of loading all repo guidance up front. .claude/rules/ makes that workflow very useful in practice: the model only sees the guidance relevant to the files it is about to touch.

This can be implemented today with hooks, but the hook version still feels like a workaround. I do not think the native Codex shape should be “support .claude/rules specifically” or reuse the term “rules”, since Codex already uses .rules for execution policy. A better native shape might be something like path-scoped instruction fragments / session-deduped additional context.

I would be happy to contribute a PR if maintainers think this belongs in core. Before writing code, I’d like to understand which direction would fit Codex best:

  1. document the hook pattern as official guidance;
  2. add a small native dedupe/key mechanism for hook additionalContext;
  3. add a native path-scoped instruction loader;
  4. keep this as a plugin/userland pattern.

My preference is native lazy path-scoped context, but I want to align with the harness design before proposing a diff.