Codex sessions causing OOM causes full WSL system crash

Open 💬 8 comments Opened Apr 16, 2026 by kendonB
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

121

What subscription do you have?

Pro

Which model were you using?

gpt-5.4 xhigh

What platform is your computer?

WSL

What terminal emulator and version are you using (if applicable)?

Windows Terminal

What issue are you seeing?

If I run out of memory in a process run by a codex agent, my wsl crashes in a way that is unable to be recovered without a restart. The standard process killer works correctly and kills the process if I run it myself in an interactive session.

What steps can reproduce the bug?

Add memory_ramp_test.py

#!/usr/bin/env python3

import datetime as dt
import mmap
import os
import signal
import sys
import time


PAGE_SIZE = os.sysconf("SC_PAGE_SIZE")


def env_int(name, default):
    raw = os.environ.get(name)
    if raw is None or raw == "":
        return default
    return int(raw)


def read_meminfo():
    out = {}
    with open("/proc/meminfo", "r", encoding="utf-8") as handle:
        for line in handle:
            key, value = line.split(":", 1)
            parts = value.strip().split()
            if parts:
                out[key] = int(parts[0])
    return out


def read_status():
    out = {}
    with open("/proc/self/status", "r", encoding="utf-8") as handle:
        for line in handle:
            key, value = line.split(":", 1)
            out[key] = value.strip()
    return out


def mib_from_kib(kib):
    return kib / 1024


def timestamp():
    return dt.datetime.now(dt.timezone.utc).astimezone().isoformat(timespec="seconds")


def log(message):
    print(f"{timestamp()} {message}", flush=True)


def touch_mapping(mapping):
    length = len(mapping)
    for offset in range(0, length, PAGE_SIZE):
        mapping[offset] = 1
    mapping[length - 1] = 1


def main():
    chunk_mib = env_int("CHUNK_MIB", 512)
    max_alloc_mib = env_int("MAX_ALLOC_MIB", 20480)
    min_available_mib = env_int("MIN_AVAILABLE_MIB", 1024)
    sleep_seconds = env_int("SLEEP_SECONDS", 10)
    hold_seconds = env_int("HOLD_SECONDS", 300)

    mappings = []
    allocated_mib = 0
    stopping = False

    def handle_signal(signum, _frame):
        nonlocal stopping
        stopping = True
        log(f"received_signal={signum}; stopping after current step")

    signal.signal(signal.SIGTERM, handle_signal)
    signal.signal(signal.SIGINT, handle_signal)

    log(
        "memory_ramp_start "
        f"pid={os.getpid()} chunk_mib={chunk_mib} max_alloc_mib={max_alloc_mib} "
        f"min_available_mib={min_available_mib} sleep_seconds={sleep_seconds} hold_seconds={hold_seconds}"
    )

    try:
        step = 0
        while allocated_mib < max_alloc_mib and not stopping:
            meminfo = read_meminfo()
            available_mib = mib_from_kib(meminfo.get("MemAvailable", 0))
            if available_mib <= min_available_mib:
                log(
                    "stop_threshold_reached "
                    f"allocated_mib={allocated_mib} mem_available_mib={available_mib:.1f}"
                )
                break

            this_chunk_mib = min(chunk_mib, max_alloc_mib - allocated_mib)
            mapping = mmap.mmap(-1, this_chunk_mib * 1024 * 1024)
            touch_mapping(mapping)
            mappings.append(mapping)
            allocated_mib += this_chunk_mib
            step += 1

            status = read_status()
            meminfo = read_meminfo()
            log(
                "step "
                f"step={step} allocated_mib={allocated_mib} "
                f"vmrss={status.get('VmRSS', 'NA')} vmhwm={status.get('VmHWM', 'NA')} "
                f"mem_available_mib={mib_from_kib(meminfo.get('MemAvailable', 0)):.1f} "
                f"mem_free_mib={mib_from_kib(meminfo.get('MemFree', 0)):.1f} "
                f"swap_free_mib={mib_from_kib(meminfo.get('SwapFree', 0)):.1f}"
            )

            time.sleep(sleep_seconds)

        log(f"holding allocated_mib={allocated_mib} hold_seconds={hold_seconds}")
        time.sleep(hold_seconds)
        log(f"memory_ramp_done allocated_mib={allocated_mib}")
    except MemoryError:
        log(f"memory_error allocated_mib={allocated_mib}")
        return 2
    except BaseException as exc:
        log(f"unexpected_error type={type(exc).__name__} message={exc}")
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())

Ask codex to run it in wsl. Wait for WSL crash.

What is the expected behavior?

The standard OS process killer kills the process.

Additional information

_No response_

View original on GitHub ↗

8 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #16828

Powered by Codex Action

raye-deng · 3 months ago

This issue highlights a critical failure mode that traditional monitoring cannot catch: AI tools can consume resources in ways that bypass normal checks and crash entire systems.

The problem is that AI-generated OOM scenarios don't look like traditional memory leaks — they're probabilistic failures that depend on model state, context size, and conversation history. Traditional memory profiling tools and static analysis can't predict when an AI agent will create a pathological context configuration that exhausts system memory.

We see similar patterns in production: AI coding tools create code that passes all traditional quality gates but causes runtime failures under specific context configurations. The unit tests pass (because they use minimal contexts), the code compiles, and SonarQube gives it a clean bill — but the system crashes in production.

One approach we've found effective: adding runtime guardrails that detect anomalous resource consumption patterns (sudden spikes, rapid context growth, session proliferation) and intervene before they cause system-level failures.

npx @opencodereview/cli scan . --sla L1 if you want to add similar safeguards to your workflow.

kendonB · 3 months ago

Don't blindly install random npm packages posted by bots team

https://www.pangram.com/history/1544ee87-bffe-4597-90d5-cfa719848af9

kendonB · 2 months ago

This is still a problem on 0.130.0

omry · 2 months ago

I hit a similar WSL failure mode with the VS Code Codex extension: Codex can consume enough memory to destabilize the whole WSL VM. A cgroup wrapper around the extension-launched Codex binary has been a useful containment workaround for me.

Important detail for VS Code + WSL: wrapping codex in $PATH may not catch the VS Code extension, because the extension can launch its own bundled native binary under:

~/.vscode-server/extensions/openai.chatgpt-*-linux-x64/bin/linux-x86_64/codex

The cleaner hook I found is the VS Code setting:

"chatgpt.cliExecutable": "/home/YOUR_USER/.local/bin/codex-contained"

Wrapper:

#!/usr/bin/env sh
set -eu

MEMORY_MAX="${CODEX_MEMORY_MAX:-12G}"
SWAP_MAX="${CODEX_SWAP_MAX:-1G}"
CPU_QUOTA="${CODEX_CPU_QUOTA:-250%}"

find_codex() {
  {
    ls -d "$HOME"/.vscode-server/extensions/openai.chatgpt-*-linux-x64/bin/linux-x86_64/codex 2>/dev/null || true
    ls -d "$HOME"/.vscode/extensions/openai.chatgpt-*-linux-x64/bin/linux-x86_64/codex 2>/dev/null || true
  } | sort -V | tail -n 1
}

REAL_CODEX="$(find_codex)"

if [ -z "$REAL_CODEX" ] || [ ! -x "$REAL_CODEX" ]; then
  echo "codex-contained: could not find an executable OpenAI VS Code Codex binary" >&2
  exit 127
fi

exec systemd-run --user --scope \
  --same-dir \
  --collect \
  --quiet \
  --unit="codex-contained-$(date +%Y%m%d-%H%M%S)-$$" \
  -p MemoryMax="$MEMORY_MAX" \
  -p MemorySwapMax="$SWAP_MAX" \
  -p CPUQuota="$CPU_QUOTA" \
  "$REAL_CODEX" "$@"

On my WSL VM, the total WSL memory is about 20 GiB, so I used:

MemoryMax=12G
MemorySwapMax=1G
CPUQuota=250%

I originally tried 10G, but the app-server startup/session restore was OOM-killed. With 12G, it has been stable so far, and current usage is far below the cap:

Memory: ~306M
Peak: ~818M
Max: 12.0G
Swap max: 1.0G

This does not fix the leak/root cause, but it prevents Codex from taking down the whole WSL instance. It should also work on regular Ubuntu 24.04 if systemd --user and cgroup v2 are available.

mirogta · 1 month ago

I had the same symptoms: Codex memory would grow until it crashed, and in my case the WSL2 VM/session also went down with it. I also tried to increase the WSL memory, enable/disable swap, to no avail, but the issue was _inside_ WSL and not in the WSL Settings.

TL;DR:

Prompt: Verify that memories are enabled and check the size of your MEMORY.md file. If it's mostly a sparse file greater than 1GB, it may crash codex _and_ WSL - then move it out to a backup, and recreate MEMORY.md from your memory_summary.md

---

Detailed Analysis and Explanation

Environment:

  • WSL2 on Windows
  • Codex CLI 0.138.0
  • Memories enabled:
[features]
memories = true

[memories]
generate_memories = true
use_memories = true

Root cause in my case appeared to be a corrupted Codex memory file ~/.codex/memories/MEMORY.md

It was about 5 GB, but the actual valid memory content was only about 15 KB. The file contained huge NUL-byte / zero-filled regions.

Useful checks:

du -h ~/.codex/memories/MEMORY.md
ls -lh ~/.codex/memories/MEMORY.md
stat -c 'apparent=%s bytes blocks=%b block_size=%B' ~/.codex/memories/MEMORY.md

Codex itself confirmed that its MEMORY.md was a sparse file with 99.9997% of NUL bytes.

In my case, Codex process RSS reached multi-GB memory usage. The evidence strongly suggests Codex was trying to read/load/index this corrupted multi-GB memory file, causing OOM and then WSL instability and crash.

This workaround that kept memories enabled and fixed this issue for me permanently:

# backup existing memory for analysis and move it out from live path
mkdir -p ~/.codex/crash-investigation-backups/$(date +%Y-%m-%d)-memory-corruption
mv ~/.codex/memories/MEMORY.md ~/.codex/crash-investigation-backups/$(date +%Y-%m-%d)-memory-corruption/MEMORY.md.corrupt
# recreate memory just from the summary
cp ~/.codex/memories/memory_summary.md ~/.codex/memories/MEMORY.md

Then verified the size with:

du -h ~/.codex/memories/MEMORY.md

After rebuilding from memory_summary.md, my live MEMORY.md was about 14 KB with zero NUL bytes.

Requests to the @openai/codex-core-agent-team :

  1. Can codex doctor flag this loudly please. In my case codex doctor was green/happy even though ~/.codex/memories/MEMORY.md was a corrupt 5 GB file. It should fail or warn on oversized memory files, sparse holes, and NUL bytes in markdown memory.
  2. Codex startup should refuse to load a multi-GB memory file and tell the user how to quarantine/rebuild it.
  3. Please investigate what can write gigabytes of NUL bytes into MEMORY.md: Codex memory generation, WSL/filesystem behavior, interrupted writes, or some interaction between them.
mirogta · 1 month ago

@xl-openai @etraut-openai @pakrym-oai Can you please escalate this as a critical bug? It is a blocker that renders Codex entirely unusable, rather than a minor performance degradation.

This issue appears to be a duplicate of #25877

omry · 1 month ago

In my case I don't even have a memory file.
I am not sure what is the root cause yet, but vscode-server is spinning adding and remove watches from my repo root (and leaking memory).
I saw some reports that this could happen for non git repos (I am using sapling) but I didnt' get to prove it yet.