openai / openai/codex

Root context lacks a cumulative hygiene boundary across self-state reads and lossy tool output

Open
#40,493 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI context
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Summary

Codex root sessions currently lack one cumulative context-hygiene boundary across two already reported behaviors:

  1. the root can read its own persisted rollout/SQLite state and re-inject it as fresh tool output; and
  2. individually bounded tool results can still accumulate, while native truncation provides no protected, recoverable artifact reference.

I reproduced the interaction on Codex CLI 0.149.0 and built a best-effort local mitigation. The mitigation reduced input for a controlled fixture and blocked tested direct self-state forms, but it also demonstrates why the complete fix must be native: shell-string matching is bypassable, PostToolUse runs after any native truncation, and replacement currently surfaces as a tool error.

Component and environment

  • Component: Codex CLI history/context manager, unified exec/tool results, session persistence, and hooks
  • Codex CLI: 0.149.0
  • Subscription: ChatGPT-authenticated; the exact tier is not exposed by the CLI
  • Platform: Darwin 25.5.0 arm64 arm (macOS 26.5.2)
  • Terminal: Apple Terminal 470.2
  • Public source inspected: tag rust-v0.149.0, commit 758ef40f50c1a458425c7cfbf1eb12cbc07af0b0
  • Observed root model: gpt-5.6-sol
  • Controlled E2E model: gpt-5.6-luna

codex doctor --json was available and reported:

{
  "schemaVersion": 1,
  "overallStatus": "warning",
  "codexVersion": "0.149.0",
  "config.load": "ok",
  "installation": "ok",
  "runtime": "standalone on macos-aarch64",
  "state.paths": "inspectable",
  "state.rollout_db_parity": "ok",
  "warning": "low disk space; unrelated to the reproduced context semantics"
}

Version 0.149.1 became available after the evidence capture. This report is intentionally scoped to the verified 0.149.0 environment rather than claiming an untested version.

Related issues

  • #27131 reports recursive self-ingestion from session JSONL.
  • #14206 requests recoverable auto-spill for large tool output.

This issue is the cross-cutting invariant exposed by their interaction: the root should receive bounded evidence references, not unrestricted self-state or an unbounded cumulative sequence of individually truncated exploratory results. If maintainers prefer to track that invariant entirely in either existing issue, this report can be closed as a synthesis after its evidence is linked there.

Reproduction and evidence chain

1. Fixed rollout and SQLite checkpoint

At immutable rollout ordinals 0..575:

Measurement Value
JSONL records 576
JSONL bytes 4,441,768
SQLite projection next byte 4,441,768
SQLite projection next ordinal 576
Normalized history items 193
Combined measured tool output 752,727 UTF-8 bytes
Last request input 271,744 tokens
Last request cached input 269,056 tokens
Reported model window 828,400 tokens
Cumulative thread processing 15,073,225 tokens

The fixed JSONL prefix hashes to f10f8d18005c75c6d5ae82a1dcfcfeadbeed4d0f9dc221114fbd5a52b2078441. The SQLite projection later advanced, so the historical byte/ordinal equality is reported as a timestamped observation rather than a permanently re-queryable fact.

Sanitized proofs:

2. Cumulative growth

A later sanitized snapshot contained 343 model-facing tool outputs totaling 1,190,101 characters. Between its first and last token samples, input grew by 474,909 tokens; the last sample occupied 61.0692% of the reported context window.

No single output needs to exceed its native cap for repeated exploratory results to consume substantial root context.

3. Recursive self-state path

The root can invoke ordinary shell tools against $CODEX_HOME/sessions/**, archived sessions, rollout JSONL, and local history SQLite files. The returned bytes are then ordinary tool output eligible for root history, creating this feedback loop:

inspect context growth
  -> search $CODEX_HOME
  -> match the current or historical transcript
  -> print transcript bytes as tool output
  -> include bounded result in later root input
  -> investigate the additional growth

This extends #27131 with SQLite projection evidence and with the cumulative effect of repeated individually bounded outputs.

Controlled mitigation experiment

I ran three alternating baseline/guarded pairs. Both arms used the same prompt, requested command contract, model, working directory, hook feature, nonce-bound temporary workspace, one successful command, byte-identical command output, and final OK. Context Mode was disabled in both arms. Only the temporary max_inline_bytes policy changed.

Results:

  • baseline artifacts: 0/3;
  • guarded artifacts: exactly one private, SHA-256-verified, non-pretruncated artifact in each run;
  • input-token pair deltas: 7,298, 7,313, and 7,306;
  • fixture mean avoided input: 7,305.67 tokens (10.1192%).

The percentage is specific to this approximately 15 KB synthetic fixture and is not claimed for general workloads. The functional result is that the guarded path preserved the requested outcome while replacing the model-facing payload with a compact receipt.

Actual behavior

  • Persisted Codex session state is readable through ordinary root tools.
  • Repeated bounded results accumulate in later root prompts.
  • Truncated bytes do not receive a stable protected artifact contract.
  • A local PostToolUse replacement appears as a tool error, and it cannot recover bytes removed before the hook runs.
  • A generic artifact path can itself become another self-ingestion surface if broad root searches rediscover it.

Expected behavior

Codex should enforce a native context-hygiene invariant:

  1. recognize the active session's own rollout, history projections, and protected artifact store;
  2. require explicit approval or metadata-only access when the root tries to read those sources;
  3. apply a cumulative root exploration budget before constructing the next sampling request;
  4. convert oversized or over-budget results to a typed artifact envelope before model-history insertion;
  5. expose bounded range/search/structured-read operations by opaque artifact ID;
  6. keep persisted audit evidence separate from model-visible content;
  7. bound successful subagent completion forwarding independently of raw child exploration.

This is an expected-behavior contract, not a claim that the local Python API is the correct upstream implementation.

Public-source causal anchors

The tagged public source shows the relevant flow:

These anchors support the causal interpretation for the public tag. I cannot prove byte-for-byte correspondence between the distributed binary and every closed service.

Privacy

The repository intentionally excludes raw rollouts, prompts, full tool output, SQLite item_json, credentials, personal paths, and thread IDs. Public artifacts contain aggregates, hashes, redacted paths, and query descriptions only.

Limits of the local mitigation

The local hook is intentionally fail-open and is not a security boundary. Shell aliases, variables, command substitution, interpreter code, unrecognized tools, malformed payloads, or hook-schema changes can bypass known-form matching. It has no automatic artifact TTL/quota, cannot cover every hosted tool path, and does not establish cross-platform or closed-service behavior.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with codex-rs/core/src/context_manager/history.rs and codex-rs/core/src/session/turn.rs, then trace tool recording in codex-rs/core/src/stream_events_utils.rs against the tagged 0.149.0 flow. Compare the reproduction metrics and expected-behavior contract with the existing history, session, and tool-result paths. Done should mean native cumulative bounds, protected artifact references, controlled self-state access, and bounded subagent forwarding without relying on the local hook.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, sqlite
Domain
ai, cli, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.