Skip to content

Global TMPDIR under XDG_RUNTIME_DIR turns stale Git snapshots into tmpfs memory pressure and OOM #1132

Description

@pujx233

Summary

Since #1070, the generated launcher sets the entire Electron process tree's default TMPDIR to:

$XDG_RUNTIME_DIR/codex-desktop/tmp

On systemd Linux desktops, XDG_RUNTIME_DIR is normally a size-limited tmpfs. The upstream Desktop Git worker also uses the inherited temporary directory for turn-diff/review snapshot repositories. When those snapshots become stale and are not removed, their Git indexes and loose objects consume Shmem, swap, and ultimately physical memory.

On one Ubuntu host, nine stale temporary Git repositories occupied about 2.8 GiB in /run/user/1000/codex-desktop/tmp. They remained for roughly 38 hours and materially contributed to a global OOM event. The kernel reported 10.66 GiB of shmem and only 124 KiB of free swap before killing an Electron renderer.

This report does not claim that #1070 created the upstream stale-snapshot lifecycle bug. It made the Linux impact substantially worse by placing all inherited temporary data, including potentially large Git snapshots, in runtime tmpfs while solving an IPC ownership problem.

Affected launcher behavior

Current main still contains:

configure_runtime_tmpdir() {
    [ -z "${TMPDIR:-}" ] || return 0

    if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -d "$XDG_RUNTIME_DIR" ] && [ -w "$XDG_RUNTIME_DIR" ]; then
        TMPDIR="$XDG_RUNTIME_DIR/$CODEX_LINUX_APP_ID/tmp"
    else
        TMPDIR="$APP_STATE_DIR/tmp"
    fi
    mkdir -p "$TMPDIR"
    chmod 700 "$TMPDIR"
    export TMPDIR
}

Source at current main head b2f676cd718eeb29a6a9b0d3feb1ec098c3acf15:

configure_runtime_tmpdir() {
[ -z "${TMPDIR:-}" ] || return 0
if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -d "$XDG_RUNTIME_DIR" ] && [ -w "$XDG_RUNTIME_DIR" ]; then
TMPDIR="$XDG_RUNTIME_DIR/$CODEX_LINUX_APP_ID/tmp"
else
TMPDIR="$APP_STATE_DIR/tmp"
fi
mkdir -p "$TMPDIR"
chmod 700 "$TMPDIR"
export TMPDIR
}
configure_runtime_tmpdir

PR #1070 correctly identified the multi-user /tmp/codex-ipc ownership collision. The problem is the scope of the mitigation: changing global TMPDIR also redirects unrelated, potentially unbounded application data into XDG_RUNTIME_DIR.

Environment

OS: Ubuntu 26.04 LTS
Kernel: 7.0.0-28-generic
Desktop: GNOME
Session: Wayland, Electron through XWayland
Package format: deb
codex-desktop-linux: 0.10.1
Port source commit: 19d3c1fbb38ac0e9be2d1da3594affd101e25353
Installed package: 2026.07.22.135939+05a76850
Upstream app: 26.715.72359
Electron: 42.3.0
Optional Linux features: none
RAM: 31 GiB
Swap: 1 GiB

Relevant mounts:

/run/user/1000 tmpfs size=3174472k mode=700 uid=1000 gid=1000
/tmp           tmpfs size=15872376k usrquota

The running app-server inherited:

TMPDIR=/run/user/1000/codex-desktop/tmp
XDG_RUNTIME_DIR=/run/user/1000
CODEX_LINUX_APP_ID=codex-desktop

Observed snapshot residue

Before cleanup:

  • Nine stale temporary directories were present under /run/user/1000/codex-desktop/tmp.
  • They were created within a few minutes of each other and remained until the following day's OOM investigation.
  • Total space was approximately 2.8 GiB.
  • Each directory contained a Git index and loose object database.
  • One inspected index contained 12,341 entries.
  • The dominant objects mapped back to two unignored CMake build outputs:
cmake-build-release/lib/exported/libshiva_client.so.2.5.0
uncompressed Git blob: 422,823,096 bytes
loose object: approximately 146.8 MiB

cmake-build-variant-binding/lib/exported/libshiva_client.so.2.5.0
uncompressed Git blob: 444,392,224 bytes
loose object: approximately 151.8 MiB

The build directory was large and untracked. That repository state amplified the problem, but a background snapshot should still have bounds and reliable cleanup.

Application logs also contained:

warning [electron-message-handler] worker_rpc_response_error
errorMessage="Git snapshot became stale"
method=turn-diff-capture-complete
workerId=git

and:

Failed to complete turn diff analytics capture
errorMessage="Git snapshot became stale"
status=completed

After the stale directories were removed, the same Codex runtime temp directory was approximately 44 MiB instead of 2.8 GiB.

OOM evidence

At 2026-07-23 15:16:15 local time, the kernel reported:

active_anon:10542492kB
inactive_anon:17998528kB
shmem:11177188kB
Free swap = 124kB
Total swap = 1048572kB

The OOM killer selected an Electron renderer:

Out of memory: Killed process 83865 (electron)
anon-rss:472596kB
shmem-rss:56220kB
oom_score_adj:300

Concurrent IDE indexing supplied the final memory burst, so the killed renderer was not itself holding all of the missing memory. The persistent baseline included the large tmpfs-backed snapshot residue.

Reproduction shape

A deterministic minimal reproducer has not yet been reduced, but the observed workload was:

  1. Run the packaged Linux Desktop app without an explicit TMPDIR.
  2. Open a Git repository containing large visible untracked/generated files.
  3. Use threads that cause turn-diff/review snapshot capture.
  4. Observe temporary Git repositories under $XDG_RUNTIME_DIR/codex-desktop/tmp.
  5. Trigger stale or cancelled snapshot work, switch threads, or leave the app running across repeated captures.
  6. Monitor the runtime directory, Shmem, and logs for Git snapshot became stale.

Useful probes:

findmnt -T "$XDG_RUNTIME_DIR"
du -sh "$XDG_RUNTIME_DIR/codex-desktop/tmp"
find "$XDG_RUNTIME_DIR/codex-desktop/tmp" -mindepth 1 -maxdepth 1 -type d -printf '%TY-%Tm-%Td %TH:%TM %p\n'
awk '/MemAvailable|Shmem|SwapTotal|SwapFree/ {print}' /proc/meminfo

Expected behavior

  • Large or potentially unbounded temporary Git data should not be placed in a size-limited runtime tmpfs by default.
  • Per-user IPC isolation should remain secure.
  • Completed, cancelled, failed, or stale snapshot work should remove its temporary Git repository.
  • Snapshot work should have size, path-count, age, or concurrency bounds so large untracked build outputs cannot exhaust host memory.

Suggested port-side mitigation

The port can mitigate this independently of the upstream closed-source snapshot lifecycle:

  1. Use a private disk-backed directory such as $XDG_CACHE_HOME/$CODEX_LINUX_APP_ID/tmp or $APP_STATE_DIR/tmp for the global TMPDIR, with mode 0700.
  2. Keep Unix sockets and other small runtime-only artifacts explicitly under $XDG_RUNTIME_DIR.
  3. Preserve the existing explicit TMPDIR override behavior.
  4. Add bounded startup cleanup for stale snapshot directories from previous app instances, where they can be identified safely.
  5. Update the existing launcher smoke test, which currently requires the runtime-scoped default.

A private disk-backed TMPDIR still addresses the cross-user /tmp/codex-ipc ownership failure from #1070 without charging bulk snapshot objects to runtime tmpfs.

Current workaround

  • Set an explicit, private, disk-backed TMPDIR before launching Desktop.
  • Ignore large generated/build directories in each repository.
  • Remove confirmed stale snapshot directories after verifying that no active snapshot operation uses them.

Related reports

I searched this repository for TMPDIR, XDG_RUNTIME_DIR, tmpfs, Shmem, Git snapshot became stale, turn-diff, and memory/OOM reports before filing and did not find an issue covering this Linux-specific tmpfs amplification path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions