Custom pets fail to load in WSL environments due to path normalization
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.
24 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Confirmed on my machine as well.
Repro: custom pet
clawdis 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.30905on@OpenAi.Plan:
Business PlanPlatform:
WindowsThis matches the path-loading behavior described in this issue.
Confirmed on my Windows + WSL2 as well.
I added a custom pet at
C:\Users\username\.codex\pets\petname\pet.jsonC:\Users\username\.codex\pets\petname\spritesheet.webpThe 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.jsonpoints tospritesheet.webp, and the spritesheet is a valid1536x1872WebP. The app Settings UI shows the custom pets folder asC:\Users\username\.codex\pets, but after Refresh and app/PC restart, the custom pet still does not appear.Environment:
OpenAI.Codex_26.429.3425.0_x640.128.0-alpha.1runCodexInWindowsSubsystemForLinuxis enabledThis appears to match the WSL path-loading behavior described here.
Confirmed
I can add a more specific reproduction and path-level finding for this issue.
## Environment
Microsoft Windows NT 10.0.26200.0 x64OpenAI.Codex_26.429.8261.0_x64__2p2nqsd0c76g026.429.61741C:\Users\<USER>\.codex\pets\<pet-id>\pet.json## Reproduction
C:\Users\<USER>\.codex\pets\<pet-id>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/petsThe WSL app-server cannot discover pets at that path.
The path needs to be normalized to the WSL mount path before joining
petsoravatars:/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/petsAfter normalizing the Codex home when
preferWslis true:/mnt/c/Users/<USER>/.codex/petsA loader-like verifier then discovered the custom pet from:
/mnt/c/Users/<USER>/.codex/pets/<pet-id>/pet.json## Suggested fix scope
When
preferWslis true and the resolved Codex home is a Windows drive path, normalize it before constructing the custompetsandavatarspaths.Conceptually:
``
ts/mnt/${codexHome[0].toLowerCase()}/${codexHomefunction normalizeCodexHomeForWsl(codexHome: string, preferWsl: boolean): string {
if (preferWsl && /^[a-zA-Z]:[\\/]/.test(codexHome)) {
return
.slice(3)
.replace(/\\/g, "/")}`;
}
return codexHome;
}
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
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
OpenAI.Codex_26.519.2081.0_x64__2p2nqsd0c76g026.519.2081.0Ubuntu-22.04CODEX_HOMEin the WSL app-server process resolves to the WSL view of the Windows Codex homeWSLENVincludesCODEX_HOME/pNote: 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
Settings > Appearance > Pets, the custom petsOpen folderaction opens the expected folder:<CODEX_HOME_WIN>\petsRefresh custom pets, no custom pets appear in the Pets list.selected-avatar-idto a custom pet folder id, for examplecustom-pet-02, is persisted in.codex-global-state.json.Wake Petor/petstill shows the built-incodexpet, 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:For each package:
pet.jsonexists.spritesheet.webpexists.pet.json.idmatches the folder name.pet.json.spritesheetPathpoints to the existingspritesheet.webp.1536x1872RGBA.Example normalized package metadata:
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:
Open folderpath resolves correctly to<CODEX_HOME_WIN>\pets.So the failing path may be specifically in the custom pet scan/list/load code path rather than in the folder-opening helper.
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:
The expected Windows path display would be:
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.:
This looks like the pets directory is being constructed with a hardcoded POSIX suffix, such as appending
/petsto a Windows%USERPROFILE%\.codexpath, 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.Still reproducible on a substantially newer Codex Desktop build as of July 18, 2026.
Environment
26200.8875, x64OpenAI.Codex_26.715.3651.0_x64__2p2nqsd0c76g06.18.33.2-microsoft-standard-WSL2codex-cli 0.144.3Reproduction / validation
A valid custom pet package exists in both Codex homes:
The metadata is valid and the folder id matches
pet.json.id:The files on the Windows and WSL sides are byte-identical:
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.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 WindowsCODEX_HOMEpath (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 thepetsdirectory 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.
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.
Update (2026-08-01)
Correction: the
selected-avatar-id-only workaround I previously posted is not reliable on current builds. On Codex Desktop26.727.6591.0with 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: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_HOMEalias 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.
Still reproducible on a newer Codex Desktop build, and the recently suggested
selected-avatar-idworkaround does not resolve it in this environment.Environment
OpenAI.Codex_26.721.3996.0_x64__2p2nqsd0c76g0runCodexInWindowsSubsystemForLinux = trueconfig.tomlalready 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:
The folder name matches
pet.json.id,spritesheetPathpoints to the existing file, and the manifest usesspriteVersionNumber: 1.Current-build inspection
The packaged
26.721.3996.0JavaScript still performs custom-pet discovery in this order:With a Windows Codex home and the WSL platform path implementation, this remains consistent with the mixed
C:\Users\...\.codex/petspath 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?https://github.com/user-attachments/assets/9acef91c-633d-4b6c-8eae-3985db8c4a55
We need Jimothy
Still reproducible on the newer Codex Desktop build
OpenAI.Codex_26.721.4979.0_x64__2p2nqsd0c76g0.Environment and package validation
runCodexInWindowsSubsystemForLinux = trueC:\Users\<USER>\.codex\pets\pearl1\{pet.json,spritesheet.webp}/home/<USER>/.codex/pets/pearl1/{pet.json,spritesheet.webp}pet.json.idmatches the folder namespriteVersionNumber: 21536x2288WebPThe proposed Windows-side selection workaround was also tested:
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:
The Electron host is a Windows process, so
WSL_DISTRO_NAMEis not set even though the configured agent environment is WSL. Consequently,preferWslis false and Codex home resolves as a Windows path. The loader then obtains the WSL/POSIX platform path implementation and joins that Windows home withpets, producing a mixed path such as:This explains why placing identical packages in both the Windows and WSL homes, refreshing, restarting, and directly selecting
custom:pearl1all fail: list and load use the same incorrect resolution boundary.Could a maintainer please:
preferWslfrom the Desktop WSL setting (or normalize the Codex home specifically in custom pet/avatar discovery) rather than the Windows Electron process environment.This has now been reproduced across multiple
26.721builds and prevents all custom-pet use for users who need to retain WSL for their development environment.Independent reproduction on the current
26.721.4979.0Windows package, with two valid v2 pets and some additional loader-level evidence.Environment
OpenAI.Codex_26.721.4979.0_x64__2p2nqsd0c76g0Ubuntu-24.04runCodexInWindowsSubsystemForLinux = truespawnCommand=wsl.exeC:\Users\<USER>\.codex/mnt/c/Users/<USER>/.codexPet packages tested
Two v2 packages were installed in both the Windows Codex home and the WSL user home:
satoru-gojosasukeFor both packages:
pet.jsonparses successfullyspritesheetPathresolves to the existingspritesheet.webpspriteVersionNumberis2VP8Ldata as exactly1536x228826.721.4979.0loader returnstrueDespite this,
Settings > Pets > Refreshdoes not list either custom pet. Mirroring the packages under/home/<USER>/.codex/petsdoes not help.Loader finding
The current package does not use a separate pet registry. It enumerates
CODEX_HOME/pets/*/pet.jsondirectly. The main-process service calls the loader with behavior equivalent to:The Windows Electron process does not reliably expose the configured Desktop WSL mode through
process.env.WSL_DISTRO_NAME, whileexecutionHostRegistry.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: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 asnull, so Refresh can silently show an empty custom-pet list even though both packages validate.This independently confirms the
26.721mixed 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.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_HOMEwith the WSL/POSIX path implementation. Giving the Windows Electron process a POSIX-formCODEX_HOMEavoids that mixed path. Two NTFS directory junctions make the same alias resolvable from both sides:.codexmakes/mnt/<drive>/.../.codex-wsl-pets-shimwork inside WSL.\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
/mnt. Ifautomount.rootwas customized in/etc/wsl.conf, change$wslMountRootin every block below.%USERPROFILE%\.codexdirectory.WSL_DISTRO_NAMEor globally set WindowsCODEX_HOMEto the POSIX alias.1. Create the two aliases once
Run this in Windows PowerShell:
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:Use the exact line printed for the machine; the example above is only illustrative. If
~/.bash_profileexists, 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:This launcher discovers the current installed package path, so normal updates on the same app drive do not require hard-coding a versioned
WindowsAppsdirectory.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\petsshould appear; no copy under/home/<wsl-user>/.codex/petsis 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
.codexbefore removing them:rmdirremoves only these two...-wsl-pets-shimjunctions, not their target or any pet/config/session data. The WSL profile's realCODEX_HOMEexport 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.
Independent reproduction on the current Windows package
OpenAI.Codex_26.727.6591.0_x64__2p2nqsd0c76g0.Environment
runCodexInWindowsSubsystemForLinux = true0.146.0-alpha.9.2spawnCommand=wsl.execustom:qingrong-alpacaPackage 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:
1536x1872pet.json.spritesheetPathresolves to the existing fileAfter 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
avatarOverlaywindow starts, the Desktop log records:The packaged loader in this build still appears to select WSL preference using behavior equivalent to:
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.727release line?Additional failure mode on
26.727.6591.0: "Create your own pet" fails, and the trigger is the spelling ofCODEX_HOMEFollowing 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
OpenAI.Codex_26.727.6591.0_x64__2p2nqsd0c76g0Ubuntu-24.04runCodexInWindowsSubsystemForLinux = truespawnCommand=wsl.exeSymptom
Settings > Pets > Create your own petappears inert. Clicking it produces only theUnable to start pet creationtoast, so it reads as a dead button. The Desktop log shows thereal failure, once per click:
Because the UI calls the installer with
forceReinstall: true, this repeats on every click evenwhen a valid
hatch-petskill is already present.Root cause
installRecommendedSkillcomposes the install destination with the app-server's POSIXpathimplementation while
codexHomemay still be in Windows spelling. A Windows-spelled path is notabsolute under POSIX rules, so
resolveprepends the Electron process working directory — thepackage folder under
C:\Program Files\WindowsApps\...— and the result is then translated to a\\wsl$\<distro>\...UNC. Creating\\wsl$\<distro>\Program Filesis refused, hence the EPERM.I reproduced the computation with Node from the app's own working directory. Only the spelling of
codexHomevaries:|
CODEX_HOMEseen 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.execannot produce the working case: everysuccessful 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 NTFSjunctions described earlier in this thread) makes the button work end to end. After that launch:
\mnt\c\Users\<USER>\.codex-wsl-pets-shim\...Create your own petinstalls the skill and pre-fills the composer as intendedFailed to install recommended skillcount for the session: 0The 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:
A useful diagnostic for anyone triaging this:
executablePath=in the Desktop log points at theCodex 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
the Electron process environment, so Windows-spelled and POSIX-spelled homes both work.
installRecommendedSkill, not just custom-pet discovery — thefix for listing does not cover creation.
permission error occurred, which is why this reads as an unresponsive button.
Confirmed still happening with latest version (July 31, 2026, Version 26.727.51351)
Feedback ID: 019fca72-34dd-7663-8221-b52cd96467ed
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/petswith 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.Reproduced on current Windows Desktop build
26.814.5517.0with WSL backend enabled. I traced the shippedapp.asarand can confirm the custom-pet failure mechanism in this build.Environment (sanitized):
The important detail is that the current call chain is slightly different from the earlier
preferWsl=truehypothesis:preferWslisfalsehere because the Windows Electron main process itself does not haveWSL_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: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.., returnsnull, and the pet entry is silently filtered beforepet.jsonis read.So in this build the observed chain is:
This also explains why there is no useful runtime error: the failing entry is intentionally converted to
nulland filtered.One implication: a fix guarded only by
preferWsl === truemay not cover the current build, because custom pet enumeration currently reaches this path withpreferWsl=falsewhileexecutionHost.platformPath()is stillpath.posix.A safer invariant seems to be: the path API used for
join/resolve/relativemust match the path representation ofcodexHome(orcodexHomemust 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.
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._
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:
26.818.5229.0, the AppX manifest declaresapp/ChatGPT.exeas the desktop UI executable.Codex.exeis 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 thanWindowsApps, 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 thecodexid 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.