Skip to content

[Bug]: Codex legacy mirrored-history fallback ignores contextTokenBudget and caps high-window sessions at ~24k rendered chars #84084

Description

@iannwu

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

In OpenClaw v2026.5.18, the legacy mirrored-history fallback in extensions/codex/src/app-server/run-attempt.ts:1088-1092 calls:

projectContextEngineAssemblyForCodex({
  assembledMessages,
  originalHistoryMessages,
  prompt,
})

without passing maxRenderedContextChars.

When omitted, projectContextEngineAssemblyForCodex normalizes the optional arg back to DEFAULT_RENDERED_CONTEXT_CHARS = 24_000 per extensions/codex/src/app-server/context-engine-projection.ts:17 and :362-369.

The active-context-engine branch already passes a budget-scaled cap at extensions/codex/src/app-server/run-attempt.ts:1024-1036:

maxRenderedContextChars: resolveCodexContextEngineProjectionMaxChars({
  contextTokenBudget: params.contextTokenBudget,
  reserveTokens: resolveCodexContextEngineProjectionReserveTokens({
    config: params.config,
  }),
})

That parity fix landed in PR #80761, closing issue #80760. It does not cover the legacy mirrored-history fallback call site.

Steps to reproduce

  1. Use OpenClaw v2026.5.18.
  2. Configure an agent using:
    provider=openai-codex
    modelApi=openai-codex-responses
    modelId=gpt-5.5
    contextTokenBudget=272000
    plugins.slots.contextEngine unset
    
  3. Because plugins.slots.contextEngine is unset, the context engine defaults to legacy per src/plugins/slots.ts.
  4. Continue a session until a new Codex thread is established while prior user turns already exist.
  5. Trigger a mid-session Codex thread rebuild, for example by clearing/losing the startup binding or by hitting a dynamic-tool fingerprint mismatch so shouldProjectMirroredHistoryForCodexStart returns true.
  6. Observe that the fallback rebuild uses the 24,000-character default projection cap rather than a cap derived from the 272,000-token budget.

Expected behavior

The legacy mirrored-history fallback should pass maxRenderedContextChars derived from contextTokenBudget, matching the active-context-engine branch behavior added in PR #80761.

For a 272,000-token budget, resolveCodexContextEngineProjectionMaxChars computes:

(contextTokenBudget - reserveTokens) * 4

clamped to:

[DEFAULT_RENDERED_CONTEXT_CHARS=24000, MAX_RENDERED_CONTEXT_CHARS=1000000]

With the default reserve, a 272,000-token budget reaches the MAX_RENDERED_CONTEXT_CHARS = 1,000,000 clamp, or roughly 250,000 tokens of rendered history capacity.

Actual behavior

The legacy mirrored-history fallback omits maxRenderedContextChars, so projectContextEngineAssemblyForCodex falls back to:

DEFAULT_RENDERED_CONTEXT_CHARS = 24000

That is roughly 6,000 tokens of rendered history. In production observations on v2026.5.18 with provider=openai-codex, modelApi=openai-codex-responses, modelId=gpt-5.5, and contextTokenBudget=272000, a channel whose behavior is consistent with legacy fallback rebuilds shows much lower retained context than a stable channel. Two snapshots from the same agent at different times, both on v2026.5.18:

Snapshot pair A (earlier):
                  Rebuild-heavy channel      Stable channel
Context           36k / 272k (13%)            118k / 272k (43%)
Compactions       0                            0

Snapshot pair B (later):
                  Rebuild-heavy channel      Stable channel
Context           36k / 272k (13%)            90k / 272k (33%)
Compactions       0                            0

The retained-context ratio is consistently in the 13% band on the rebuild-heavy channel and in the 33-43% band on the stable channel. The rebuild-heavy channel was observed not to grow past ~36k of total context usage, which is consistent with each new-thread turn re-rendering projected history at the 24,000-character cap plus normal system/tool/current-message overhead.

The fresh-input size is consistent with the code path:

24,000 rendered chars ~= 6,000 tokens of projected history
+ ~25k-30k tokens of system prompt, developer instructions, tool schemas, and current user message
= ~31k tokens fresh input per fallback turn

OpenClaw version

v2026.5.18

Operating system

Darwin 25.4.0

Install method

npm global

Model

openai-codex/gpt-5.5 via Codex app-server
modelApi=openai-codex-responses
contextTokenBudget=272000

Provider / routing chain

openclaw -> openai-codex (Codex OAuth) -> OpenAI Codex Responses API

Additional provider/model setup details

effective plugins.slots.contextEngine = "legacy"

plugins.slots.contextEngine is unset in the user openclaw.json. legacy is the resolved default per src/plugins/slots.ts:17-20 (default contextEngine: "legacy"). No third-party context-engine plugin installed.

Relevant prior merged work:

#80760 / PR #80761:
Active context-engine branch was changed to pass a context-budget-scaled
maxRenderedContextChars. The same fix was not applied to the legacy fallback branch.

#83127:
Fixed truncation direction inside projectContextEngineAssemblyForCodex via the
new truncateOlderContext helper (context-engine-projection.ts:385-402).
Applies to both branches because the helper is inside the projection function.

#83466 (issue) / PR #83516 / commit 491ce8b753:
Fixed inbound image attachment delivery on provider=openai modelApi=openai-responses.
Does not address this legacy-fallback projection-budget gap.

Logs, screenshots, and evidence

v2026.5.18 source references:

extensions/codex/src/app-server/run-attempt.ts:1088-1092
Legacy mirrored-history fallback calls projectContextEngineAssemblyForCodex without maxRenderedContextChars.

extensions/codex/src/app-server/run-attempt.ts:1024-1036
Active-context-engine branch passes maxRenderedContextChars derived from contextTokenBudget and reserveTokens.

extensions/codex/src/app-server/context-engine-projection.ts:17
Defines DEFAULT_RENDERED_CONTEXT_CHARS = 24_000.

extensions/codex/src/app-server/context-engine-projection.ts:29-40, :362-369
Normalizes missing/optional maxRenderedContextChars to DEFAULT_RENDERED_CONTEXT_CHARS.

extensions/codex/src/app-server/context-engine-projection.ts:385-402
Tail-preserving truncateOlderContext helper from #83127, applied to both branches.

src/plugins/slots.ts:17-20
Default `contextEngine: "legacy"` when no slot or plugin selection is configured.

Empirical production observation on v2026.5.18 (two consecutive snapshots across two channels under the same agent, same workspace, same OpenAI Codex OAuth profile):

Configuration (both channels):
provider=openai-codex
modelApi=openai-codex-responses
modelId=gpt-5.5
contextTokenBudget=272000
effective plugins.slots.contextEngine=legacy

Channel A (rebuild-heavy):
Snapshot 1: Context 36k/272k (13%), Compactions 0
Snapshot 2: Context 36k/272k (13%), Compactions 0

Channel B (stable thread):
Snapshot 1: Context 118k/272k (43%), Compactions 0
Snapshot 2: Context 90k/272k (33%), Compactions 0

Suggested fix shape, not normative:

Copy the existing maxRenderedContextChars kwarg from run-attempt.ts:1024-1036
into the legacy fallback call at run-attempt.ts:1088-1092.

No new helper appears necessary because the active branch already has the required expression.

Impact and severity

The default legacy context-engine path can rebuild Codex mirrored history using a 24,000-character projection cap even when the model has a 272,000-token context budget. Affected: any agent on the Codex app-server runtime with default legacy context-engine config (no third-party context-engine plugin installed) on long-running channels that re-establish Codex threads mid-session.

Observed impact in v2026.5.18 production:

Context retained held at 36k / 272k (13%) on the rebuild-heavy channel
across two snapshots taken on the same day.

The same agent on a different channel without projection rebuilds reached
90-118k / 272k (33-43%) in the same period.

No compactions occurred, so the observed reduction was not caused by compaction.

Active-context-engine users already receive the budget-scaled cap from PR #80761. The gap is limited to the legacy mirrored-history fallback call site.

Additional information

Scope guard: this issue is only about the projection budget used by the legacy mirrored-history fallback.

The separate root cause for why some channels frequently re-establish Codex threads is not part of this issue and should be filed separately after this lands. One observed trigger shape is dynamic-tool fingerprint churn when room_event inbound forces message_tool_only and changes the message tool direct/deferred spec, but that is not part of this issue and would benefit from its own investigation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.no-staleExclude from stale automation

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions