Custom pets fail to load in WSL environments due to path normalization

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

Summary

Custom Pets appear to not work in WSL-enabled environments. This may be caused by Windows/WSL path normalization for the custom pets directory.

Environment

  • Windows host with WSL-enabled Codex environment
  • Custom Pets enabled/configured

Observed behavior

Custom Pets do not load correctly when running Codex from a WSL-enabled environment.

The suspected problem is that Codex is resolving or normalizing the custom pet asset path incorrectly instead of looking under the expected \pets path.

Expected behavior

Custom Pets should work in WSL-enabled environments. Codex should normalize/translate the custom pet path consistently between the Windows host and WSL/Linux runtime so the configured pet assets can be located.

Possible cause

Path normalization between Windows host paths and WSL/Linux paths may be incomplete for custom pet assets. The custom pet lookup may need to normalize to the expected pets directory before attempting to load pet assets.

Notes

This issue is specifically about Custom Pets failing in WSL-enabled environments. A previous local screenshot read error was unrelated to the underlying Custom Pets behavior and has been removed from this report.

View original on GitHub ↗

24 Comments

github-actions[bot] contributor · 3 months ago

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

  • #20014

Powered by Codex Action

wahyuabrory · 3 months ago

Confirmed on my machine as well.

Repro: custom pet clawd is present under ~/.codex/pets/clawd/{pet.json,spritesheet.webp} and appears normally when Codex is used from Windows native, but it does not appear in Settings when Codex is run from a WSL2 environment.

Observed with Codex 26.429.30905 on @OpenAi.
Plan: Business Plan
Platform: Windows

This matches the path-loading behavior described in this issue.

chisong1 · 3 months ago

Confirmed on my Windows + WSL2 as well.

I added a custom pet at

C:\Users\username\.codex\pets\petname\pet.json
C:\Users\username\.codex\pets\petname\spritesheet.webp

The same files are also present under the WSL home:

/home/username/.codex/pets/petname/{pet.json,spritesheet.webp}

Codex notes that the package validates locally per the hatch pet skill: pet.json points to spritesheet.webp, and the spritesheet is a valid 1536x1872 WebP. The app Settings UI shows the custom pets folder as C:\Users\username\.codex\pets, but after Refresh and app/PC restart, the custom pet still does not appear.

Environment:

  • Windows host with WSL2 enabled
  • Codex Desktop package path shows OpenAI.Codex_26.429.3425.0_x64
  • App server reports 0.128.0-alpha.1
  • runCodexInWindowsSubsystemForLinux is enabled

This appears to match the WSL path-loading behavior described here.

FabCodeDE · 3 months ago

Confirmed

RomeoRaven · 3 months ago

I can add a more specific reproduction and path-level finding for this issue.

## Environment

  • OS: Microsoft Windows 11 Pro, build 26200, x64
  • Requested PowerShell output: Microsoft Windows NT 10.0.26200.0 x64
  • Codex Desktop package: OpenAI.Codex_26.429.8261.0_x64__2p2nqsd0c76g0
  • App version observed in package: 26.429.61741
  • App-server configured to run through WSL
  • Custom pet installed under the Windows Codex home:

C:\Users\<USER>\.codex\pets\<pet-id>\pet.json

## Reproduction

  1. On Windows, create a valid custom pet under:

C:\Users\<USER>\.codex\pets\<pet-id>

  1. Open Codex Desktop in Windows native mode.
  2. Go to Settings -> Appearance -> Pets and click Refresh.
  3. The custom pet appears.
  4. Switch Codex Desktop to run in WSL mode.
  5. Restart Codex Desktop.
  6. Go back to Settings -> Appearance -> Pets and click Refresh.
  7. The custom pet does not appear.

This matches the behavior reported in #20905, but with a more specific path-normalization finding.

## Path finding

In WSL mode, the custom pet loader appears to construct a mixed Windows/POSIX path like:

C:\Users\<USER>\.codex/pets

The WSL app-server cannot discover pets at that path.

The path needs to be normalized to the WSL mount path before joining pets or avatars:

/mnt/c/Users/<USER>/.codex/pets

## Local verification

A local dry-run ASAR patch confirmed the narrow behavior change.

Before:

path.posix.join("C:\\Users\\<USER>\\.codex", "pets")
=> C:\Users\<USER>\.codex/pets

After normalizing the Codex home when preferWsl is true:

/mnt/c/Users/<USER>/.codex/pets

A loader-like verifier then discovered the custom pet from:

/mnt/c/Users/<USER>/.codex/pets/<pet-id>/pet.json

## Suggested fix scope

When preferWsl is true and the resolved Codex home is a Windows drive path, normalize it before constructing the custom pets and avatars paths.

Conceptually:

``ts
function normalizeCodexHomeForWsl(codexHome: string, preferWsl: boolean): string {
if (preferWsl && /^[a-zA-Z]:[\\/]/.test(codexHome)) {
return
/mnt/${codexHome[0].toLowerCase()}/${codexHome
.slice(3)
.replace(/\\/g, "/")}`;
}
return codexHome;
}


  This should be scoped to custom pet/avatar discovery and should not change global CODEX_HOME, app-server launch behavior, terminal command execution, workspace paths, skills, agents, or file browsing helpers
Tairitsua · 3 months ago

Appreciate the idea @RomeoRaven
If anybody need this urgently, you can use my temporary patch script for now. 😂
https://github.com/Tairitsua/codex-wsl-custom-pets-patch

yui-stingray · 3 months ago

I can add one data point from a newer Windows build. This does not establish a new root cause, but it narrows the behavior a bit: the UI can open the expected custom pets folder, while the custom pet list/renderer still does not pick up valid packages from that same folder.

Environment

  • Codex Desktop package: OpenAI.Codex_26.519.2081.0_x64__2p2nqsd0c76g0
  • Codex Desktop version: 26.519.2081.0
  • Windows host with WSL integration enabled
  • WSL distro: Ubuntu-22.04
  • CODEX_HOME in the WSL app-server process resolves to the WSL view of the Windows Codex home
  • WSLENV includes CODEX_HOME/p

Note: this is not a clean-install environment. It is a real Windows + WSL Codex Desktop profile that has previously had Desktop/WSL path and local history troubleshooting. I am including this because the issue still reproduces after that profile is in a stable state where local history opens correctly.

Additional observation

  • In Settings > Appearance > Pets, the custom pets Open folder action opens the expected folder:
  • <CODEX_HOME_WIN>\pets
  • That folder contains multiple valid custom pet packages.
  • After clicking Refresh custom pets, no custom pets appear in the Pets list.
  • Setting selected-avatar-id to a custom pet folder id, for example custom-pet-02, is persisted in .codex-global-state.json.
  • Running Wake Pet or /pet still shows the built-in codex pet, so the renderer appears to fall back after failing to resolve/list/load the selected custom pet.

Local package validation

I tested three packages under <CODEX_HOME_WIN>\pets:

custom-pet-01
custom-pet-02
custom-pet-03

For each package:

  • pet.json exists.
  • spritesheet.webp exists.
  • pet.json.id matches the folder name.
  • pet.json.spritesheetPath points to the existing spritesheet.webp.
  • The spritesheet opens as 1536x1872 RGBA.

Example normalized package metadata:

[
  {
    "folder": "custom-pet-01",
    "id": "custom-pet-01",
    "displayName": "Custom Pet 01",
    "spritesheetPath": "spritesheet.webp",
    "idMatchesFolder": true,
    "spritesheetExists": true
  },
  {
    "folder": "custom-pet-02",
    "id": "custom-pet-02",
    "displayName": "Custom Pet 02",
    "spritesheetPath": "spritesheet.webp",
    "idMatchesFolder": true,
    "spritesheetExists": true
  },
  {
    "folder": "custom-pet-03",
    "id": "custom-pet-03",
    "displayName": "Custom Pet 03",
    "spritesheetPath": "spritesheet.webp",
    "idMatchesFolder": true,
    "spritesheetExists": true
  }
]

Why this may be useful

Earlier comments already show that custom pets work in Windows native mode but not in WSL mode, and one comment points to a mixed path such as C:\Users\<USER>\.codex/pets.

This newer-build data point suggests there may be two separately visible paths in the UI:

  1. The Open folder path resolves correctly to <CODEX_HOME_WIN>\pets.
  2. The refresh/list/load path still fails to discover the same valid packages and falls back to the built-in pet.

So the failing path may be specifically in the custom pet scan/list/load code path rather than in the folder-opening helper.

petercr · 2 months ago

I can confirm this is still reproducible in Codex Desktop on Windows.

In Settings > Appearance, the Custom Pets location is displayed with mixed path separators:

C:\Users\<user>\.codex/pets

The expected Windows path display would be:

C:\Users\<user>\.codex\pets

Or, if this path is being passed to the renderer for asset loading, it likely needs to be converted to a proper file URL first, e.g.:

file:///C:/Users/<user>/.codex/pets/...

This looks like the pets directory is being constructed with a hardcoded POSIX suffix, such as appending /pets to a Windows %USERPROFILE%\.codex path, instead of using platform-aware path joining and URL conversion. Custom pets are discovered/shown in the UI, but the mixed root path appears consistent with the load failure.

leewaiho · 1 month ago

Still reproducible on a substantially newer Codex Desktop build as of July 18, 2026.

Environment

  • Windows 11 Pro 25H2, build 26200.8875, x64
  • Codex Desktop package: OpenAI.Codex_26.715.3651.0_x64__2p2nqsd0c76g0
  • WSL2: Ubuntu 22.04, kernel 6.18.33.2-microsoft-standard-WSL2
  • Codex CLI/app-server side: codex-cli 0.144.3
  • Codex Desktop is configured to use WSL

Reproduction / validation

A valid custom pet package exists in both Codex homes:

<WSL_CODEX_HOME>/pets/rourou/pet.json
<WSL_CODEX_HOME>/pets/rourou/spritesheet.webp

<WINDOWS_CODEX_HOME>/pets/rourou/pet.json
<WINDOWS_CODEX_HOME>/pets/rourou/spritesheet.webp

The metadata is valid and the folder id matches pet.json.id:

{
  "id": "rourou",
  "displayName": "肉肉 Rourou",
  "spritesheetPath": "spritesheet.webp"
}

The files on the Windows and WSL sides are byte-identical:

pet.json          SHA-256 9806da6574bc412ae98320fce75240c1277b71a4122bc6ab48af50761e1f6ccc
spritesheet.webp  SHA-256 09f494c0689bf14505f978b54596e8eada105e152fee865bc61d476697d24870

Despite this, refreshing custom pets in Windows Codex Desktop while the app-server is running through WSL does not make the custom pet available/loadable. The built-in pet remains the only usable pet.

This confirms the problem is still present in Desktop 26.715.3651.0; it is not caused by a missing package or Windows/WSL copies being out of sync. It remains consistent with the WSL custom-pet discovery/path-normalization failure described in this issue.

Elevation-mindset · 1 month ago

Reproduced on Codex Desktop 26.715.4045.0 on Windows with the Codex app server running in WSL.

I had 37 valid custom pet packages under %USERPROFILE%\.codex\pets. Settings showed built-in pets only, with no error. The failure was caused by custom-pet discovery combining a Windows CODEX_HOME path (C:\...) with POSIX path operations from the WSL app server; the resulting mixed path fails and the loader silently returns an empty custom-pet list.

A user-local patched copy that normalizes the Windows drive path to /mnt/c/... before joining the pets directory loaded all 37 pets successfully, including Zoro, Obito, and Sasuke. Selecting pets then worked normally.

One extra detail: a separate patched desktop executable must be launched only after fully exiting stock Codex, otherwise the app's single-instance handoff reopens the unpatched WindowsApps build.

malibudawley · 1 month ago

Still reproducible in Codex Desktop 26.715.10079.0 on Windows with a WSL-backed agent environment. A valid v1 pet at C:\Users\<USER>\.codex\pets\jimothy passes the app’s manifest, dimensions, and WebP parser checks, but Custom pets remains empty after refresh and full restart. Mirroring it to /home/<USER>/.codex/pets/jimothy does not help. This appears to confirm the mixed Windows/POSIX path-normalization bug described here. Please prioritize a fix.

Sicari1 · 1 month ago

Update (2026-08-01)

Correction: the selected-avatar-id-only workaround I previously posted is not reliable on current builds. On Codex Desktop 26.727.6591.0 with a WSL-backed app-server, the selection persisted, but custom-pet discovery and loading still failed because the loader produced a mixed Windows/POSIX path such as:

C:\Users\<USER>\.codex/pets

A verified, reversible workaround that keeps the agent environment in WSL is documented here:

https://github.com/openai/codex/issues/20730#issuecomment-5107481538

In short, it keeps the WSL app-server on the real shared Codex home and launches the stock Desktop app with a per-process POSIX CODEX_HOME alias backed by NTFS junctions. After fully restarting Desktop and refreshing Pets, valid custom pets were listed and rendered successfully.

No WSL restart, ASAR patch, permanent Windows environment-variable change, or duplicate pet package is required. The underlying path-normalization issue remains open.

linvva · 1 month ago

Still reproducible on a newer Codex Desktop build, and the recently suggested selected-avatar-id workaround does not resolve it in this environment.

Environment

  • Windows 11 host
  • Codex Desktop package: OpenAI.Codex_26.721.3996.0_x64__2p2nqsd0c76g0
  • WSL2 distro: Debian
  • runCodexInWindowsSubsystemForLinux = true
  • Windows-side config.toml already has the top-level setting:

``toml
selected-avatar-id = "custom:liuying-firefly-pixel"
``

Package validation

The custom pet is installed under the Windows Codex home and passes the current Hatch Pet validator:

format: WEBP
mode: RGBA
width: 1536
height: 1872
transparent_rgb_residue_pixels: 0
errors: []
warnings: []

The folder name matches pet.json.id, spritesheetPath points to the existing file, and the manifest uses spriteVersionNumber: 1.

Current-build inspection

The packaged 26.721.3996.0 JavaScript still performs custom-pet discovery in this order:

const codexHome = resolveCodexHome({ preferWsl });
const platformPath = await appServerClient.platformPath();
const petsDir = platformPath.join(codexHome, "pets");
const avatarsDir = platformPath.join(codexHome, "avatars");

With a Windows Codex home and the WSL platform path implementation, this remains consistent with the mixed C:\Users\...\.codex/pets path described in this issue. Refreshing custom pets and fully restarting Desktop do not make the pet load.

This issue has now reproduced across multiple release lines through 26.721, while the only reliable workarounds are switching the entire agent environment back to Windows native or running a patched copy of Desktop. Could the team please prioritize normalizing the Codex home specifically for custom pet/avatar discovery in WSL mode, or share whether a fix is planned?

charlesdavid · 1 month ago

Still reproducible on the newer Codex Desktop build OpenAI.Codex_26.721.4979.0_x64__2p2nqsd0c76g0.

Environment and package validation

  • Windows host with Codex Desktop configured with runCodexInWindowsSubsystemForLinux = true
  • Valid v2 custom pet installed in both locations:
  • C:\Users\<USER>\.codex\pets\pearl1\{pet.json,spritesheet.webp}
  • /home/<USER>/.codex/pets/pearl1/{pet.json,spritesheet.webp}
  • pet.json.id matches the folder name
  • spriteVersionNumber: 2
  • Spritesheet is a valid 1536x2288 WebP
  • Refresh and full process restart do not list or activate the pet
  • Creating/loading additional custom pets is affected as well

The proposed Windows-side selection workaround was also tested:

selected-avatar-id = "custom:pearl1"

The selection persists, but the pet still cannot be resolved or rendered.

Current-build inspection

The packaged desktop code calls the custom-avatar loader with behavior equivalent to:

load({ preferWsl: !!process.env.WSL_DISTRO_NAME })

The Electron host is a Windows process, so WSL_DISTRO_NAME is not set even though the configured agent environment is WSL. Consequently, preferWsl is false and Codex home resolves as a Windows path. The loader then obtains the WSL/POSIX platform path implementation and joins that Windows home with pets, producing a mixed path such as:

C:\Users\<USER>\.codex/pets

This explains why placing identical packages in both the Windows and WSL homes, refreshing, restarting, and directly selecting custom:pearl1 all fail: list and load use the same incorrect resolution boundary.

Could a maintainer please:

  1. Confirm ownership or planned status for this regression.
  2. Derive preferWsl from the Desktop WSL setting (or normalize the Codex home specifically in custom pet/avatar discovery) rather than the Windows Electron process environment.
  3. Add a Windows-host/WSL-app-server integration test for custom pet list and load.
  4. Post continuing updates here when the issue is triaged, assigned, fixed, or available in a test/release build.

This has now been reproduced across multiple 26.721 builds and prevents all custom-pet use for users who need to retain WSL for their development environment.

lgonzalezm-dev · 1 month ago

Independent reproduction on the current 26.721.4979.0 Windows package, with two valid v2 pets and some additional loader-level evidence.

Environment

  • Codex Desktop package: OpenAI.Codex_26.721.4979.0_x64__2p2nqsd0c76g0
  • Windows host, WSL2 distro: Ubuntu-24.04
  • runCodexInWindowsSubsystemForLinux = true
  • Desktop logs confirm the local app-server is started with spawnCommand=wsl.exe
  • Windows Codex home: C:\Users\<USER>\.codex
  • WSL sees that home as /mnt/c/Users/<USER>/.codex

Pet packages tested

Two v2 packages were installed in both the Windows Codex home and the WSL user home:

  • satoru-gojo
  • sasuke

For both packages:

  • pet.json parses successfully
  • spritesheetPath resolves to the existing spritesheet.webp
  • spriteVersionNumber is 2
  • the packaged app's own WebP parser recognizes the VP8L data as exactly 1536x2288
  • the same acceptance predicate used by the 26.721.4979.0 loader returns true

Despite this, Settings > Pets > Refresh does not list either custom pet. Mirroring the packages under /home/<USER>/.codex/pets does not help.

Loader finding

The current package does not use a separate pet registry. It enumerates CODEX_HOME/pets/*/pet.json directly. The main-process service calls the loader with behavior equivalent to:

load({
  appServerClient: executionHostRegistry.get(),
  preferWsl: !!process.env.WSL_DISTRO_NAME,
})

The Windows Electron process does not reliably expose the configured Desktop WSL mode through process.env.WSL_DISTRO_NAME, while executionHostRegistry.get() is backed by the WSL/POSIX app-server host. The loader can therefore join a Windows home with a POSIX path implementation and construct:

C:\Users\<USER>\.codex/pets

One additional reason this is difficult to diagnose from logs: the loader wraps creation/access of the computed pets directory in a broad try/catch; on failure it returns { avatarDirectory, avatars: [] }. Individual manifest/load failures are also caught and returned as null, so Refresh can silently show an empty custom-pet list even though both packages validate.

This independently confirms the 26.721 mixed Windows/POSIX resolution boundary. Deriving the pet loader's path flavor from the selected execution host/Desktop WSL setting, rather than the Electron process environment, should address both list and load.

matthewclso · 1 month ago

I found and locally validated a non-patched, reversible workaround for users who need to keep Codex Desktop's agent environment set to WSL. It uses the stock Microsoft Store package: no ASAR changes, unsigned executable, package replacement, or pet-file duplication.

Validated on OpenAI.Codex_26.721.4979.0_x64__2p2nqsd0c76g0. Both custom-pet listing and loading worked through the workaround.

Why it works

The current loader effectively combines a Windows CODEX_HOME with the WSL/POSIX path implementation. Giving the Windows Electron process a POSIX-form CODEX_HOME avoids that mixed path. Two NTFS directory junctions make the same alias resolvable from both sides:

  1. A junction beside the real Windows .codex makes /mnt/<drive>/.../.codex-wsl-pets-shim work inside WSL.
  2. A second junction under the app drive's \mnt\... tree handles Windows-side Node calls that interpret /mnt/... as a root-relative Windows path.

The WSL app-server itself remains on the real shared Codex home, preventing config/auth/session state from splitting between Windows and WSL.

Important assumptions and cautions

  • The scripts derive the Windows username, profile location, profile drive, and current Codex package location automatically.
  • They assume the normal WSL drive mount root, /mnt. If automount.root was customized in /etc/wsl.conf, change $wslMountRoot in every block below.
  • The setup refuses to replace any existing non-matching path.
  • Do not replace or delete the real %USERPROFILE%\.codex directory.
  • Do not globally set WSL_DISTRO_NAME or globally set Windows CODEX_HOME to the POSIX alias.

1. Create the two aliases once

Run this in Windows PowerShell:

$ErrorActionPreference = 'Stop'
$wslMountRoot = '/mnt' # Change only if WSL automount.root is customized.
$wslMountRoot = $wslMountRoot.TrimEnd('/')

$package = Get-AppxPackage OpenAI.Codex |
    Sort-Object Version -Descending |
    Select-Object -First 1
if ($null -eq $package) {
    throw 'The OpenAI.Codex Appx package was not found for this Windows user.'
}

$realHome = Join-Path $env:USERPROFILE '.codex'
if (-not (Test-Path -LiteralPath $realHome -PathType Container)) {
    throw "Codex home does not exist: $realHome"
}

$profileRoot = [IO.Path]::GetPathRoot($env:USERPROFILE)
$profileDrive = $profileRoot.Substring(0, 1).ToLowerInvariant()
$relativeProfile = $env:USERPROFILE.Substring($profileRoot.Length).Replace('\', '/')

$wslRealHome = "$wslMountRoot/$profileDrive/$relativeProfile/.codex"
$wslShim = "$wslRealHome-wsl-pets-shim"
$userShim = "$realHome-wsl-pets-shim"

# A Windows process resolves a leading / against its current drive. The launcher
# below uses the package directory as its working directory, so mirror the
# POSIX-looking alias under that drive as well.
$appDriveRoot = [IO.Path]::GetPathRoot($package.InstallLocation)
$windowsShim = Join-Path $appDriveRoot ($wslShim.TrimStart('/').Replace('/', '\'))

function Ensure-Junction {
    param([string]$Path, [string]$Target)

    $item = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue
    if ($null -ne $item) {
        $targets = @($item.Target) |
            ForEach-Object { ([string]$_).TrimEnd('\') }
        if (($item.LinkType -ne 'Junction') -or
            ($targets -notcontains $Target.TrimEnd('\'))) {
            throw "Refusing to replace existing path: $Path"
        }
        Write-Host "Already correct: $Path -> $Target"
        return
    }

    New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Path) |
        Out-Null
    New-Item -ItemType Junction -Path $Path -Target $Target |
        Out-Null
    Write-Host "Created: $Path -> $Target"
}

Ensure-Junction -Path $userShim -Target $realHome
Ensure-Junction -Path $windowsShim -Target $realHome

Write-Host ''
Write-Host 'In the WSL distro used by Codex, add this to its login-shell profile:'
Write-Host "export CODEX_HOME='$wslRealHome'"
Write-Host ''
Write-Host "Per-launch Windows CODEX_HOME will be: $wslShim"

Directory junctions normally do not require Developer Mode. If creating the parent under the app drive fails with Access denied, rerun this one-time setup in an elevated PowerShell window.

2. Keep the WSL app-server on the real shared home

In the same WSL distro Codex Desktop uses, add the export CODEX_HOME=... line printed by the setup script to the active login-shell profile.

Usually this is ~/.profile:

export CODEX_HOME='/mnt/c/Users/YOUR_WINDOWS_PROFILE/.codex'

Use the exact line printed for the machine; the example above is only illustrative. If ~/.bash_profile exists, add it there instead or make it source ~/.profile.

This step matters because Desktop launches the WSL app-server through a login shell. It ensures the WSL process continues using the real Windows-backed Codex home for configuration, authentication, sessions, skills, and pets, while only Electron sees the alias.

3. Launch the stock app with the per-process alias

Fully quit Codex Desktop first, including its tray/background process. Because Desktop is single-instance, launching while an old process remains will hand off to that process and ignore the new environment.

Save the following as, for example, Start-Codex-WslPets.ps1, or paste it into PowerShell whenever starting Codex:

$ErrorActionPreference = 'Stop'
$wslMountRoot = '/mnt' # Must match the setup script.
$wslMountRoot = $wslMountRoot.TrimEnd('/')

if (Get-Process ChatGPT -ErrorAction SilentlyContinue) {
    throw 'Quit Codex Desktop completely, including its tray process, then rerun.'
}

$package = Get-AppxPackage OpenAI.Codex |
    Sort-Object Version -Descending |
    Select-Object -First 1
if ($null -eq $package) {
    throw 'The OpenAI.Codex Appx package was not found.'
}

$profileRoot = [IO.Path]::GetPathRoot($env:USERPROFILE)
$profileDrive = $profileRoot.Substring(0, 1).ToLowerInvariant()
$relativeProfile = $env:USERPROFILE.Substring($profileRoot.Length).Replace('\', '/')

$env:CODEX_HOME = "$wslMountRoot/$profileDrive/$relativeProfile/.codex-wsl-pets-shim"

$exe = Join-Path $package.InstallLocation 'app\ChatGPT.exe'
if (-not (Test-Path -LiteralPath $exe)) {
    throw "Codex executable was not found at: $exe"
}

Start-Process -FilePath $exe -WorkingDirectory (Split-Path -Parent $exe)

This launcher discovers the current installed package path, so normal updates on the same app drive do not require hard-coding a versioned WindowsApps directory.

After launch, leave the agent environment set to WSL, then open Settings → Pets and click Refresh custom pets. Existing custom pets under the real %USERPROFILE%\.codex\pets should appear; no copy under /home/<wsl-user>/.codex/pets is needed.

The launcher must be used after each complete exit while the affected build is installed. It changes only the child process environment and does not set a permanent Windows environment variable.

Rollback

Fully quit Codex. Run the following in PowerShell; it verifies both paths are junctions targeting the real .codex before removing them:

$ErrorActionPreference = 'Stop'
$wslMountRoot = '/mnt' # Must match setup.
$wslMountRoot = $wslMountRoot.TrimEnd('/')

$package = Get-AppxPackage OpenAI.Codex |
    Sort-Object Version -Descending |
    Select-Object -First 1
$realHome = Join-Path $env:USERPROFILE '.codex'
$userShim = "$realHome-wsl-pets-shim"

$profileRoot = [IO.Path]::GetPathRoot($env:USERPROFILE)
$profileDrive = $profileRoot.Substring(0, 1).ToLowerInvariant()
$relativeProfile = $env:USERPROFILE.Substring($profileRoot.Length).Replace('\', '/')
$wslShim = "$wslMountRoot/$profileDrive/$relativeProfile/.codex-wsl-pets-shim"
$appDriveRoot = [IO.Path]::GetPathRoot($package.InstallLocation)
$windowsShim = Join-Path $appDriveRoot ($wslShim.TrimStart('/').Replace('/', '\'))

foreach ($shim in @($userShim, $windowsShim)) {
    $item = Get-Item -LiteralPath $shim -Force -ErrorAction SilentlyContinue
    if ($null -eq $item) {
        continue
    }

    $targets = @($item.Target) |
        ForEach-Object { ([string]$_).TrimEnd('\') }
    if (($item.LinkType -ne 'Junction') -or
        ($targets -notcontains $realHome.TrimEnd('\'))) {
        throw "Refusing to remove non-matching path: $shim"
    }

    & $env:ComSpec /d /c "rmdir `"$shim`""
    if ($LASTEXITCODE -ne 0) {
        throw "Failed to remove junction: $shim"
    }
    Write-Host "Removed junction: $shim"
}

rmdir removes only these two ...-wsl-pets-shim junctions, not their target or any pet/config/session data. The WSL profile's real CODEX_HOME export can remain; remove that line only if Windows and WSL should no longer share a Codex home.

After a Codex update, first try launching normally. If custom pets work with WSL without this launcher, roll back the aliases and delete the launcher.

ChinFish · 24 days ago

Independent reproduction on the current Windows package OpenAI.Codex_26.727.6591.0_x64__2p2nqsd0c76g0.

Environment

  • Windows host with WSL2 distro: Ubuntu
  • Desktop setting: runCodexInWindowsSubsystemForLinux = true
  • App-server version reported at startup: 0.146.0-alpha.9.2
  • Desktop log confirms the app-server is launched with spawnCommand=wsl.exe
  • Selected avatar persists as custom:qingrong-alpaca

Package validation

A V1 custom pet was installed in both locations:

  • C:\Users\<USER>\.codex\pets\qingrong-alpaca\{pet.json,spritesheet.webp}
  • /home/<USER>/.codex/pets/qingrong-alpaca/{pet.json,spritesheet.webp}

The two copies have matching SHA-256 hashes. The spritesheet passes the current Hatch Pet validator:

  • WebP / RGBA
  • 1536x1872
  • zero validation errors and warnings
  • pet.json.spritesheetPath resolves to the existing file

After Refresh, full tray exit, and restart, Desktop still renders the built-in default pet instead of the selected custom pet.

Runtime evidence

Immediately after the avatarOverlay window starts, the Desktop log records:

method=fs/readFile
error={"code":-32603,"message":"No such file or directory (os error 2)"}
rendererWindowAppearance=avatarOverlay

The packaged loader in this build still appears to select WSL preference using behavior equivalent to:

preferWsl: !!process.env.WSL_DISTRO_NAME

while the Electron host is a Windows process and the execution host is the WSL/POSIX app-server. This remains consistent with the mixed Windows/POSIX custom-pet path described in this issue.

This also looks like a regression or incomplete fix relative to #21471, which was closed as completed. Could the team please re-check custom pet discovery/loading with WSL-backed Desktop on the current 26.727 release line?

Sicari1 · 23 days ago

Additional failure mode on 26.727.6591.0: "Create your own pet" fails, and the trigger is the spelling of CODEX_HOME

Following up on my earlier comment. This is a distinct symptom from the list/render failures reported so far, and it isolates the boundary to a single condition.

Environment

  • Codex Desktop package: OpenAI.Codex_26.727.6591.0_x64__2p2nqsd0c76g0
  • Windows host, WSL2 distro: Ubuntu-24.04
  • runCodexInWindowsSubsystemForLinux = true
  • Desktop log confirms the app-server is launched with spawnCommand=wsl.exe

Symptom

Settings > Pets > Create your own pet appears inert. Clicking it produces only the
Unable to start pet creation toast, so it reads as a dead button. The Desktop log shows the
real failure, once per click:

warning [electron-fetch-handler] Failed to install recommended skill
error="EPERM: operation not permitted, mkdir '\\wsl$\Ubuntu-24.04\Program Files'"

Because the UI calls the installer with forceReinstall: true, this repeats on every click even
when a valid hatch-pet skill is already present.

Root cause

installRecommendedSkill composes the install destination with the app-server's POSIX path
implementation while codexHome may still be in Windows spelling. A Windows-spelled path is not
absolute under POSIX rules, so resolve prepends the Electron process working directory — the
package folder under C:\Program Files\WindowsApps\... — and the result is then translated to a
\\wsl$\<distro>\... UNC. Creating \\wsl$\<distro>\Program Files is refused, hence the EPERM.

I reproduced the computation with Node from the app's own working directory. Only the spelling of
codexHome varies:

| CODEX_HOME seen by the Electron process | computed install destination |
|---|---|
| C:\Users\<USER>\.codex | \\wsl$\Ubuntu-24.04\Program Files\... → EPERM |
| \\wsl.localhost\<distro>\home\<user>\.codex | \\wsl$\Ubuntu-24.04\Program Files\... → EPERM |
| /mnt/c/Users/<USER>/.codex | C:\Users\<USER>\.codex\skills\hatch-pet → correct |

Note that resolving the WSL Codex home through wsl.exe cannot produce the working case: every
successful branch of that lookup returns a Windows drive path or a \\wsl$\/\\wsl.localhost\ UNC,
both of which land in the broken column above.

Verification

Launching the stock package with a POSIX-form CODEX_HOME (the per-process alias plus NTFS
junctions described earlier in this thread) makes the button work end to end. After that launch:

  • the Desktop log records the Codex home as \mnt\c\Users\<USER>\.codex-wsl-pets-shim\...
  • clicking Create your own pet installs the skill and pre-fills the composer as intended
  • Failed to install recommended skill count for the session: 0

The same click on the same build, launched normally, fails with the EPERM above.

Why the workaround keeps lapsing

The alias is a per-process environment value, so it disappears when Desktop exits. Any subsequent
launch from the Start menu, a shortcut, or an auto-restart reverts to the Windows-spelled home and
the button breaks again. The NTFS junctions persist; only the variable pointing at them is lost.
Launch history from one machine:

08-01 15:02  .codex-wsl-pets-shim        (workaround launch — working)
08-03 00:31  C:\Users\<USER>\.codex      (reverted — broken)
08-04 22:26  C:\Users\<USER>\.codex      (reverted — broken)
08-04 22:38  .codex-wsl-pets-shim        (workaround launch — working)

A useful diagnostic for anyone triaging this: executablePath= in the Desktop log points at the
Codex home in use. If it shows a Windows-spelled .codex, custom-pet creation will fail.

One caveat for users applying the workaround: a POSIX-form value is interpreted as a
drive-relative path by Windows processes, so it only resolves for processes running on the drive
that holds the junction. Processes launched from another drive will not find the home.

Requests

  1. Derive the path flavor for pet/skill installation from the selected execution host rather than

the Electron process environment, so Windows-spelled and POSIX-spelled homes both work.

  1. Apply the same boundary fix to installRecommendedSkill, not just custom-pet discovery — the

fix for listing does not cover creation.

  1. Surface the installer error in the UI. The toast text gives no indication that a filesystem

permission error occurred, which is why this reads as an unresponsive button.

nelittle · 23 days ago

Confirmed still happening with latest version (July 31, 2026, Version 26.727.51351)

Feedback ID: 019fca72-34dd-7663-8221-b52cd96467ed

amiibot · 16 days ago
Appreciate the idea @RomeoRaven If anybody need this urgently, you can use my temporary patch script for now. 😂 https://github.com/Tairitsua/codex-wsl-custom-pets-patch

In the latest Codex app version, the issue still exists where pets are not read correctly when the working environment is set to WSL2. It appears as the custom pets path in the Codex App showing C:\Users\username\.codex/pets with mixed slashes. Based on my analysis of this patch, it seems to be addressing the same root cause. Unfortunately, the patch does not apply to the latest Codex app version. I also tried to build a new patch myself, but couldn't succeed due to the messy permission restrictions in the WindowsApp directory.

Hadeanyolo · 8 days ago

Reproduced on current Windows Desktop build 26.814.5517.0 with WSL backend enabled. I traced the shipped app.asar and can confirm the custom-pet failure mechanism in this build.

Environment (sanitized):

Codex Desktop: OpenAI.Codex 26.814.5517.0
platform: win32 (Electron main process)
WSL backend: enabled
CODEX_HOME: C:\Users\<USER>\.codex
existing valid pet: C:\Users\<USER>\.codex\pets\<pet-id>

The important detail is that the current call chain is slightly different from the earlier preferWsl=true hypothesis:

pets-settings
  -> customAvatars.load()
  -> getCodexHome({ preferWsl: false })
       = C:\Users\<USER>\.codex
  -> executionHost.platformPath()
       = path.posix   (local host is marked runsInsideWsl=true)
  -> path.posix.join(codexHome, "pets")
       = C:\Users\<USER>\.codex/pets

preferWsl is false here because the Windows Electron main process itself does not have WSL_DISTRO_NAME, even though the configured execution backend is WSL.

The mixed path is then used by the custom-pet enumeration logic. The directory itself can still be read by Windows native fs, but each pet entry goes through a path-safety helper equivalent to:

path.posix.resolve(petsDir, petId)
path.posix.relative(petsDir, resolvedPath)

Because C:\Users\... is not an absolute path under POSIX semantics, the resolved path is treated as relative to the Electron process cwd. The subsequent relative-path safety check sees a path beginning with .., returns null, and the pet entry is silently filtered before pet.json is read.

So in this build the observed chain is:

Windows CODEX_HOME
  + POSIX path API
  -> C:\Users\<USER>\.codex/pets
  -> POSIX resolve/relative safety check
  -> pet entry rejected
  -> custom pet list empty

This also explains why there is no useful runtime error: the failing entry is intentionally converted to null and filtered.

One implication: a fix guarded only by preferWsl === true may not cover the current build, because custom pet enumeration currently reaches this path with preferWsl=false while executionHost.platformPath() is still path.posix.

A safer invariant seems to be: the path API used for join/resolve/relative must match the path representation of codexHome (or codexHome must first be normalized to the execution host's native representation), and the same representation must be preserved through the safety check.

No files/configuration were modified during this investigation.

RomeoRaven · 5 days ago

Following the newer independent reproduction on Desktop 26.814.5517.0, is any additional sanitized diagnostic, reproduction, or package-validation evidence needed from our side to help triage this WSL custom-pet path issue? We can provide targeted evidence without repeating the root-cause material already present in the thread.

_Prepared by Hermes with AI assistance._

NBrKn · 4 days ago

I spent my codex usage so that you don't have to. Updated patch for latest version, I have added my own personal pet in there because I am planning on keeping it updated till it gets fixed. It also has the option to replace the main codex pet with your custom one (as a revenge?).

I ran into two changes that matter for current Windows builds when using Codex with WSL:

  1. The WSL path problem now affects three custom-pet operations—listing, loading the selected pet, and creating a pet—so patching only the older loader call site is incomplete.
  2. In package 26.818.5229.0, the AppX manifest declares app/ChatGPT.exe as the desktop UI executable. Codex.exe is a helper, so shortcuts that target it appear to do nothing.

I put a version-locked, reversible implementation at https://github.com/NBrKn/codex-wsl-pets-patch. It discovers the launcher from AppxManifest.xml, checks every input and output by SHA-256, patches a side-by-side copy rather than WindowsApps, uses an isolated profile to avoid the Store app's single-instance lock, verifies all three call sites, and includes safe removal. It also has an optional built-in replacement that keeps the codex id but swaps the visible pet and spritesheet for Ember.

The patch intentionally refuses unknown Codex versions. For a newer Store build, the bundle signatures and checksums need to be re-verified rather than force-applied.