Skip to content

[Bug]: responses-state-spill/ grows unbounded on a busy hub — 830 files / 270MB in ~2 days, no eviction, restart does not drain (2.58.0) #5369

Closed

Description

@tizerluo

Description

On a long-running Linux hub (systemd user service, ocx start --port 10100, moderate multi-client traffic), the durable spill directory ~/.opencodex/responses-state-spill/ grows without any apparent retention or eviction. The main responses-state.json stays roughly stable (~24MB) while the spill directory accumulates one *.spill.json file per response and is never cleaned up. A full service restart does not drain or prune it.

Environment

  • opencodex 2.58.0 (npm @bitkyc08/opencodex), Linux x64, bun runtime
  • Hub deployment: systemd user unit, data-plane port + management plane, 4–6 chained clients, mix of Codex/CLI traffic
  • Upgrade path: 2.42.0 → 2.58.0 on 2026-09-18 (the fleet ran 2.42.0 before with no spill growth: the directory did not exist at any noticeable size before the upgrade)

Observed timeline (from daily ~/.opencodex archives)

Date spill dir size file count
2026-09-18 ~0 (not present in top-10 du) —
2026-09-19 49MB ~150
2026-09-20 270MB 830

File naming pattern: resp_<hash>.<hash>.<hash>.<int>.<bytes>.spill.json, one per response, avg ~325KB. responses-state.json meanwhile: 17MB → 24MB → 24MB (stable — growth moved to the spill dir).

Reproduction / evidence

  1. Let the hub serve normal traffic for ~2 days on 2.58.0.
  2. du -sh ~/.opencodex/responses-state-spill → 270MB, ls | wc -l → 830.
  3. systemctl --user restart opencodex-proxy, wait 60s.
  4. Re-measure: still 270MB / 830 files. Zero spill/drain lines in service.log before or after restart.

Expected behavior

v2.39.0 shipped "Bound the durable spill directory with an aggregate byte cap" (#3097), and v2.58.0 shipped "enforce the shutdown drain reserve on the injected spill clock" (#4833). Either an aggregate cap or a shutdown/periodic drain should bound this directory. Neither appears to apply to these files on a hub that stays up and keeps serving traffic.

Impact

  • Daily config archives of ~/.opencodex ballooned from ~1MB to 107MB (compressed) in three days; storage cost compounds with no ceiling.
  • ~/.opencodex reached 314MB total on day 3. Left alone this is a slow disk leak on any always-on deployment.

Notes

Activity

  1. Ingwannu commented on Sep 21, 2026

    @Ingwannu
    Owner

    I checked the shipped 2.58.0/current implementation rather than inferring from the directory size. The response-state store already has three independent retention bounds:

    • MAX_SPILLED_RESPONSE_BYTES = 1 GiB
    • MAX_STORED_RESPONSES = 1000
    • RESPONSE_TTL_MS = 24h

    The spill budget is enforced on mutation, lazy restart load, and the periodic sweep. A restart intentionally preserves referenced spill files because they are the durable continuation state; “restart did not drain it” is therefore not itself a failure.

    The reported 270 MiB / 830 files is below both hard ceilings, so the current evidence does not establish unbounded growth. I am leaving this open for one bounded follow-up rather than closing it as invalid: please re-measure after the oldest cohort has exceeded 24 hours and report whether the directory exceeds 1 GiB, exceeds 1000 owned entries, or retains an owned entry whose response createdAt is older than the TTL. File mtime alone is not sufficient because a live response can be rewritten/referenced.

    If none of those boundaries is crossed, the behavior is bounded but operationally surprising; that would be a diagnostics/documentation request rather than a disk-leak bug. Please do not manually delete individual spill files while their entries remain referenced in responses-state.json.

  2. tizerluo commented on Sep 21, 2026

    @tizerluo
    ContributorAuthor

    Re-measured on the same hub after the oldest cohort exceeded the 24h TTL. None of the three boundaries is crossed:

    • Bytes: spill dir totals 332 MiB (cap 1024 MiB).
    • Owned entries: 54 spill files are referenced by name in responses-state.json (cap 1000). The other 1045 files on disk are unreferenced.
    • TTL: owned entries' response createdAt ages range 3.3–3.9 h — none over 24 h.

    The sweep is demonstrably working: every file from the initial 09-19 cohort (~49 MiB, observed before any of this reporting) is gone; the oldest surviving file's mtime is now ~38 h, and the age histogram is 845 files in 0–24 h / 254 files in 24–48 h / 0 files beyond 48 h. So the earlier 0 → 49 → 270 → 332 MiB curve was ramp-up toward a traffic-dependent plateau (~last 38 h of responses), not unbounded growth. I'll take the correction on "restart should drain" as well — understood that preserving referenced spill across restart is the durable continuation state.

    The operationally surprising part, for whatever the diagnostics/docs follow-up is worth: ~95% of the directory's bytes are unreferenced files that linger up to ~38 h before the sweep removes them. On a busy always-on hub that's a ~330 MiB steady-state footprint of dead weight (and it dominates anything that archives ~/.opencodex wholesale). If unreferenced spill files have no continuation value, sweeping them on the same 24 h cadence as ownership (or on the mutation-path budget check) would shrink the footprint several-fold without touching any owned entry.

    No manual deletion performed, per your note. Happy to re-test if a candidate build changes the sweep cadence.

  3. added
    priority: P0Critical: security/credential exposure, auth bypass, data loss, or unbounded resource growth on a ru
    on Sep 24, 2026
  4. devin-ai-integration commented on Sep 25, 2026

    @devin-ai-integration
    Contributor

    New matched pull request: #5825 (would resolve this issue) — fix: P0 #5369 spill growth + P1 bug batch [priority: P0]

  5. lidge-jun commented on Sep 25, 2026

    @lidge-jun
    Owner

    Fixed by #5825 (22b22ae): orphaned spill files are now reclaimed on the liveness tick with a bounded sweep, and the snapshot budget keeps spill stubs and tombstones ahead of resident entries, so their files are no longer orphaned after a restart. ocx doctor and /api/system/memory now report spill directory usage too.

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

    bugSomething isn't workingpriority: P0Critical: security/credential exposure, auth bypass, data loss, or unbounded resource growth on a ru

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions