Skip to content

Codex file-change diffs are duplicated in SQLite and exhaust backend heap #12758

Description

@alexfertel

What happened

The T3 Code desktop backend still stops with a V8 out-of-memory error during Codex work. The UI then marks active work as failed and reports that the provider session did not survive a server restart.

This continued after the fix for #10924 shipped. That fix is present and limits provider event log records, but large Codex file-change diffs still enter durable SQLite activity data through a separate path.

Diagnosis

The fix from #12305 works for diagnostic provider logs. Post-fix provider log records on this install stay near the 64 KiB limit. The build that crashed also contains the #12305 merge commit.

The remaining unbounded path is durable activity persistence:

  1. CodexAdapter.mapItemLifecycle attaches the full native lifecycle payload as payload.data.
  2. ProviderRuntimeIngestion copies that data into both tool.started and tool.completed activities.
  3. The resulting thread.activity-appended payload is stored in orchestration_events.
  4. The projection pipeline stores the same activity payload again in projection_thread_activities.
  5. Thread detail reads limit activity count to 500 and decode in batches of 25, but they do not limit bytes. projectActivityPayload drops unused diff data only after SQLite and V8 decode the full JSON.

The affected database is healthy but 15.9 GiB. It contains 12,551 file-change activity rows whose first changes[].diff field uses 5.2 GiB in each table, for 10.4 GiB across the event and projection copies. One stored diff has 49,359,322 characters. One activity row reaches 54.2 MiB. The newest 500 activities for the largest thread contain 571.7 MiB of raw JSON before decoding and projection.

The backend then aborts when V8 reaches its heap limit. Restarting clears live memory but leaves the large stored payloads, so the failure can return.

The current installed source still has this persistence path. A newer nightly exists, but no later commit has changed CodexAdapter, ProviderRuntimeIngestion, ProjectionSnapshotQuery, ActivityPayloadProjection, or the database migrations involved here.

Expected behavior: file-change lifecycle activities should persist bounded metadata such as paths, status, and omitted byte counts. They should not persist full diff text in both lifecycle rows and both SQLite stores. Existing oversized rows also need a supported repair or migration because the logger fix cannot clean stored data.

Steps to reproduce

Source-level reproduction:

  1. Start the desktop app with a local backend and the Codex provider.
  2. Send a valid Codex file-change item/started and item/completed lifecycle event whose item.changes[].diff contains a large string.
  3. Let CodexAdapter map the event and let ProviderRuntimeIngestion create activities.
  4. Inspect the stored thread.activity-appended event and its projected activity row.
  5. Observe that the full diff exists in both orchestration_events.payload_json and projection_thread_activities.payload_json. Both lifecycle rows can retain the payload.
  6. Repeat with tool-heavy work, or load a thread whose newest 500 activities contain several large file-change payloads.
  7. Observe rapid database growth and eventual backend heap exhaustion while T3 processes the large activity window.

A focused regression test can use a generated diff string and assert that neither persisted copy contains the full string.

Version

Current install: 0.0.43-nightly.20260920.2005, commit 7445aa733ada.

Confirmed crashes: 0.0.43-nightly.20260918.1895, commit 994654198d58. This tag is 27 commits ahead of the #12305 merge commit, so the provider logger fix was present.

Environment

  • T3 Code desktop app with local backend
  • Darwin 25.6.0, arm64
  • Node.js 26.8.2
  • Codex provider

Evidence

SQLite quick_check: ok
Database size: 15,924.2 MiB

orchestration_events:              7,907.9 MiB
projection_thread_activities:      7,737.3 MiB

thread.activity-appended payloads: 7,705.3 MiB across 209,288 rows
tool.completed payloads:           4,372.8 MiB across 54,865 rows
tool.started payloads:             3,234.9 MiB across 54,914 rows

File-change diff fields in projection:
  rows:                             12,551
  total diff text:                  5,202.4 MiB
  largest diff:                     48.8 MiB

The event table contains the same row count and 5,202.4 MiB of diff text.

Largest complete activity row:     54.2 MiB
Largest newest-500 activity window: 571.7 MiB

Three backend crashes on the confirmed build:
  EXC_CRASH / SIGABRT
  FATAL ERROR: JavaScript heap out of memory
  Last-resort retained heaps ranged from about 1.6 to 1.95 GiB.

Post-fix provider log records remained bounded near 64 KiB.
No home paths, thread IDs, turn IDs, project names, commands, or payload text are included.

Related issues

Fix applied or workaround

No database write or local source patch was made. Restarting the desktop app lowers memory use for a time. Avoiding the affected heavy threads reduces the chance of another abort, but the oversized SQLite rows remain.

Filed by

Codex, GPT-5, via t3 triage

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions