Primary runtime @oai/artifact-tool eagerly loads all user fonts on macOS, causing multi-GB Node memory usage and presentations plugin hangs

Open 💬 0 comments Opened Jun 27, 2026 by tsukumijima

What version of the Codex App are you using (From “About Codex” dialog)?

Version 26.623.42026 (Observed primary runtime: - Node: v24.14.0 - @oai/artifact-tool: 2.8.14 - @oai/walnut: 0.1.164)

What subscription do you have?

ChatGPT Pro (20x, $200)

What platform is your computer?

Darwin 23.6.0 arm64 (macOS 14.8.7, MacBook Pro (2021), RAM 16GB)

What issue are you seeing?

The local reproduction below was observed on macOS with a large user font library under ~/Library/Fonts.

Codex Desktop primary-runtime artifact workflows can trigger very large Node memory usage on macOS before any real document or presentation work starts.

The immediate trigger is importing the root @oai/artifact-tool package used by the primary-runtime presentations, documents, and spreadsheets skills.

In this environment, the following minimal import hangs or becomes extremely memory-heavy:

/Users/<user>/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
  --input-type=module \
  -e "console.error('before import'); const mod = await import('@oai/artifact-tool'); console.error('after import', Object.keys(mod).length)"

Activity Monitor previously showed node processes around 15 GB to 17 GB while this was happening. The machine also entered heavy memory pressure and swap usage.

After investigation, this appears to be caused by @oai/artifact-tool eagerly loading fonts at top-level import time. The bundled dist/artifact_tool.mjs imports FontLibrary from skia-canvas, recursively scans default font roots, and calls the font loading path during module evaluation. On macOS, those default roots include:

~/Library/Fonts
/Library/Fonts
/System/Library/Fonts
/opt/homebrew/share/fonts
/usr/local/share/fonts

In my environment, the font library is large:

~/Library/Fonts: 3177 font files, about 11289.2 MB
/Library/Fonts: 147 font files, about 386.8 MB
/System/Library/Fonts: 367 font files, about 787.8 MB

So a plain import('@oai/artifact-tool') causes skia-canvas / CoreText / CoreGraphics to process thousands of local font files before the actual presentation or document task begins.

A process sample taken while the import was stuck showed execution inside skia.node and macOS font APIs, including frames such as:

skia.node
SkDynamicMemoryWStream::write
SkFontMgr_Mac::onMakeFromStream...
SkTypeface_Mac::onGetFamilyName
CTFontCopyFamilyName
CoreText
CoreGraphics

This means the failure is not caused by a large PPTX, PDF, or slide image. It happens before the deck generation code runs.

Important narrower findings:

  • import('skia-canvas') alone completed quickly and used only a small amount of memory.
  • import('@oai/walnut/wasm/dotnet.js') alone completed quickly and used only a small amount of memory.
  • import('@oai/artifact-tool/presentation-jsx') alone completed quickly.
  • The problematic path is the root @oai/artifact-tool module evaluation.
  • Running the same root import with HOME=/var/empty completed in a few seconds because ~/Library/Fonts was no longer visible.
  • Running a minimal PresentationFile.exportPptx() with HOME=/var/empty also completed successfully.

So the issue appears to be an environment-dependent memory and startup-time problem caused by eager top-level font loading in the primary-runtime artifact engine.

What steps can reproduce the bug?

This reproduces on a macOS machine with a large ~/Library/Fonts directory.

  1. Use Codex Desktop with the primary runtime installed.
  1. Confirm the bundled Node path exists:
NODE="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
"$NODE" --version

Observed:

v24.14.0
  1. Locate the bundled artifact-tool package:
PKG="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/.pnpm/@oai+artifact-tool@file+local-deps+-oai-artifact-tool-oai-artifact_tool-2.8.14.tgz/node_modules/@oai/artifact-tool"
  1. Run the minimal import canary:
cd "$PKG"

"$NODE" --input-type=module -e "
  console.error('before import');
  const mod = await import('@oai/artifact-tool');
  console.error('after import', Object.keys(mod).length);
"
  1. Watch the Node process in Activity Monitor, ps, or footprint.

In my environment, the import did not reach after import under the normal HOME. Memory climbed heavily, and earlier Activity Monitor screenshots showed node processes around 15 GB to 17 GB.

  1. Compare with an isolated home directory:
cd "$PKG"

HOME=/var/empty "$NODE" --input-type=module -e "
  console.error('before import');
  const mod = await import('@oai/artifact-tool');
  console.error('after import', Object.keys(mod).length);
"

Observed with HOME=/var/empty:

before import
after 1170
  1. Confirm that a minimal PPTX export works when the user font directory is hidden:
cd "$PKG"

HOME=/var/empty "$NODE" --input-type=module -e "
  const { Presentation, PresentationFile } = await import('@oai/artifact-tool');
  console.error('imported');

  const presentation = Presentation.create({ slideSize: { width: 13.333, height: 7.5 } });
  const slide = presentation.slides.add();
  slide.background.fill = { type: 'solid', color: '#FFFFFF' };

  const blob = await PresentationFile.exportPptx(presentation);
  console.log(JSON.stringify({ bytes: blob.data.length, mime: blob.mimeType }));
"

Observed:

imported
{"bytes":11651}

This strongly suggests the root cause is eager font discovery and loading during @oai/artifact-tool import.

What is the expected behavior?

Importing @oai/artifact-tool should be bounded in memory and time, even on macOS machines with large user font libraries.

Expected behavior:

  • The root module import should not recursively preload every user, system, and Homebrew font at top-level module evaluation time.
  • Font discovery should be lazy, scoped to the fonts needed by the active document or presentation, or guarded by a strict limit.
  • The primary-runtime skills should not require users to hide ~/Library/Fonts or override HOME to keep memory bounded.
  • If font loading is required, the runtime should expose an explicit supported environment variable or config option that disables default roots and uses only a curated font directory.
  • A single import should not drive Node to multi-GB or 15 GB plus memory usage before any artifact has been generated.

Additional information

The primary-runtime package appears to be a private bundled OpenAI package:

{
  "name": "@oai/artifact-tool",
  "version": "2.8.14",
  "private": true,
  "dependencies": {
    "skia-canvas": "^3.0.6",
    "@oai/walnut": "0.1.164"
  }
}

A public architecture write-up that matches the observed local package structure is here:

https://github.com/hewliyang/reversing/tree/main/chatgpt-artifact-tools

Relevant local observations:

  • @oai/walnut is the .NET / OpenXML WASM backend.
  • skia-canvas is the native rendering and font path.
  • @oai/artifact-tool/presentation-jsx imports successfully and is not the problematic module.
  • The root @oai/artifact-tool bundle is the problematic import path.
  • The root bundle calls the font loading path during module evaluation.
  • On macOS, the default font roots include ~/Library/Fonts.
  • In my environment, ~/Library/Fonts contains 3177 font files totaling about 11.3 GB.
  • Hiding the user font directory with HOME=/var/empty allows the import and a minimal PPTX export to complete.

A practical workaround for affected users is to run artifact-tool based builders with a sanitized home and a curated font directory, for example:

HOME=/var/empty \
SKIA_FONT_ROOTS=/path/to/small-curated-fonts \
/Users/<user>/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node builder.mjs

However, this is only a workaround. The runtime should not eagerly load a user's entire font library during package import.

Related but not identical issues I found while searching:

  • #29119: Presentations plugin runtime missing on Windows
  • #26419: Windows Node REPL MCP failure breaking artifact runtime
  • #27120: Codex Desktop indexing primary-runtime cache paths during background git work
  • #26738: Codex Desktop memory runaway on macOS involving helper and child process trees

I did not find an existing issue specifically mentioning SKIA_FONT_ROOTS, FontLibrary, or skia-canvas font preloading in @oai/artifact-tool.

View original on GitHub ↗