app-server: feedback log sqlite (logs_N.sqlite) grows unbounded — ~0.75 GB/day, no retention/rotation

Open 💬 1 comment Opened Jun 4, 2026 by 2vanm

Summary

The app-server's internal feedback/telemetry log database ($CODEX_HOME/logs_<N>.sqlite) grows without bound — no retention, no rotation, no size cap. On a long-running headless deployment (app-server driven 24/7) it reached 9.8 GB in ~10 days (~0.75 GB/day) before we noticed and manually cleared it.

This is distinct from state_<N>.sqlite (actual thread/session state), which stays small and healthy (~106 MB).

Environment

  • codex-cli 0.132.0 (codex app-server --listen stdio://)
  • Linux x86_64 (musl vendor build), driven continuously by an automation host (frequent short sessions: heartbeats, scheduled jobs, chat).
  • CODEX_HOME set to a persistent dir.

Evidence

logs_<N>.sqlite has a single dominant table logs:

1,420,123 rows, ~9.4 GB used (10.47 GB file, 1.08 GB freelist)
schema: id, ts, ts_nanos, level, target, feedback_log_body, module_path, file, line, thread_id, process_uuid, estimated_bytes

Bytes by column — feedback_log_body dominates at 7.3 GB. By target:

 5.88 GB  n=314,237  codex_api::endpoint::responses_websocket   (TRACE)
 0.44 GB  n=329,301  codex_otel.log_only
 0.39 GB  n=324,112  codex_otel.trace_safe
 0.21 GB  n=215,142  codex_api::sse::responses
 0.21 GB  n=    453  codex_core::client

Level mix: 675,801 INFO + 614,236 TRACE + 73,883 WARN + 23,110 DEBUG + 7 ERROR.

The largest single rows are ~800 KB each — full op.dispatch.compact / op.dispatch.user_input_with_turn_context request/response payloads serialised verbatim into feedback_log_body.

Steady-state growth (one row of the per-day breakdown):

2026-05-26  140,239 rows  0.71 GB
2026-05-27  136,379 rows  0.72 GB
...
2026-06-03  139,209 rows  0.75 GB

The logs_<N> suffix appears to be a schema version, not a size-based rotation — within a version the table grows indefinitely. RUST_LOG does not appear to gate these sqlite writes (no EnvFilter hook found for the feedback-log layer), and the only "bounded" reference in the binary (bounded_feedback_logs AS (...)) is a read-side CTE used when assembling a feedback bundle, not a write-side cap.

Impact

  • ~0.75 GB/day ≈ 22 GB/month, ~270 GB/year of pure telemetry on disk for any long-lived app-server deployment.
  • Full prompt/response payloads (including large compaction turns) are persisted indefinitely on the local disk — also a data-retention concern.

Expected behaviour

The feedback log should be bounded by default — e.g. a max age and/or max size with automatic pruning/rotation (à la a rolling window), or an explicit CODEX_HOME/config setting to cap or disable it. Ideally TRACE-level full-payload dumps should not be persisted to the on-disk feedback DB by default.

Workaround

logs_<N>.sqlite* is safe to delete while the app-server is stopped — it is recreated empty on next start and only debug/feedback-bundle history is lost. But this requires manual intervention and downtime since the file is held open continuously.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗