Skip to content

[App/Windows/WSL] git-init-watcher retries permanent EISDIR on workspace root every five seconds #32695

Open

Description

@loraldx

Summary

Codex Desktop 26.707.8168.0 on Windows 11 repeatedly retries a Windows-side file watcher after receiving EISDIR for a WSL2 workspace root. The retry interval eventually settles at approximately five seconds and continues while the application remains open.

Related to #32183, but this reproduces without using the in-app browser and appears to involve the workspace-level git-init-watcher rather than only the browser-related git-repo-watcher path.

Sanitized observation

Workspace form:

\wsl$<distro>home<user><workspace>

Observed behavior:

  • Windows Node fs.watch against the WSL workspace root returns EISDIR.
  • Watching the nested Git repository root produces the same result.
  • The behavior also reproduces through the equivalent \wsl.localhost path and a mapped drive.
  • The git-init-watcher retries after failure, with exponential delay capped at approximately five seconds.
  • One application run recorded 121 failures in about ten minutes.
  • Historical application logs contained more than 17,000 failures from the same error family.
  • Git and repository operations performed inside WSL itself continue to work normally.

User names, repository names, task identifiers, full paths, and raw logs have been omitted.

Steps to reproduce

  1. On Windows 11 with WSL2, open a Git workspace located under the Linux home directory in Codex Desktop.
  2. Use a workspace path exposed through \wsl$ or \wsl.localhost.
  3. Leave the desktop application open.
  4. Inspect the Codex application logs for git-init-watcher or file-watch failures.

Actual behavior

A permanent EISDIR watcher failure enters an effectively unbounded retry loop, producing repeated work and log churn approximately every five seconds.

Expected behavior

  • Detect unsupported or permanent watcher errors such as EISDIR.
  • Fall back to bounded polling when appropriate, or disable that watcher after a small number of failures.
  • Avoid retrying permanent watcher failures indefinitely.
  • Prefer a WSL-native watcher when the workspace is handled by the WSL execution backend.

Environment

  • Codex Desktop: 26.707.8168.0
  • OS: Windows 11 x64
  • WSL2: Ubuntu
  • Workspace stored in the Linux filesystem under the WSL home directory
  • Reproduces without invoking the in-app browser

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    windows-osIssues related to Codex on Windows systems
    on Jul 13, 2026
  2. github-actions commented on Jul 13, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. gilmarvitor commented on Jul 16, 2026

    @gilmarvitor

    Adding another independent reproduction of this same behavior.

    Environment:

    • Codex Desktop/MSIX: OpenAI.Codex 26.707.9981.0
      • Runtime reported in logs: 26.707.72221
        • Codex CLI: 0.144.2
          • Windows 11, build 10.0.26200.8875, x64
            • WSL 2.7.10, Ubuntu 24.04, kernel 6.18.33.2
              • Agent configured to run in WSL2; workspace on ext4 under /home, with no Git repository initialized
                Observed behavior:
    • The Windows-side process attempts to watch a path shaped like \\wsl$\Ubuntu\home\<wsl-user>\workspace\<workspace>.
      • fs.watch repeatedly fails with EISDIR.
        • The retry occurs approximately every 5 seconds, at roughly 12 warnings per minute — consistent with the ~121 failures in ~10 minutes already reported in this issue.
          I don't consider the following to be fixes, only workarounds: running git init just to silence the watcher; keeping an artificial Windows project open; moving projects to Windows; or switching the agent to native Windows mode.

    This reinforces the suggestion already noted here: detect the permanent error and apply backoff/disable the watcher, or run the watcher in the same environment as the filesystem (WSL), instead of retrying EISDIR indefinitely. I don't have confirmation this is the root cause — this is only another reproduction of the symptom.

  4. stephanelepain-hub commented on Aug 16, 2026

    @stephanelepain-hub

    Same defect reproduces on Linux with a different errno — ENOENT instead of EISDIR — which suggests the retry logic, not the platform or the specific error, is the problem.

    Environment

    App Codex Desktop for Linux, chatgpt-26.810.52044-1.x86_64 (official RPM)
    OS Fedora 44, GNOME / Wayland, x86_64
    Kernel 7.1.8
    Workspace plain local ext4 path — no WSL, no SMB, no mapped drive

    Observation

    The recorded workspace root simply does not exist on this host (it belongs to a different machine). fs.watch() returns ENOENT, and git-init-watcher retries indefinitely:

    [git-init-watcher] Failed to watch workspace root for git init
        cwd=/home/<user>/git/<project> errorCode=ENOENT
        errorMessage="ENOENT: no such file or directory, watch '/home/<user>/git/<project>'"
        at e.evaluateImpl (/usr/lib/chatgpt/resources/app.asar/.vite/build/worker.js:200:24538)
        at e.evaluate     (/usr/lib/chatgpt/resources/app.asar/.vite/build/worker.js:200:23689)
        at e1.readLoop    (/usr/lib/chatgpt/resources/app.asar/.vite/build/worker.js:200:37425)
    

    Measured 55 occurrences in 30 minutes (~one every 5s), matching the cap described in the original report. A second independent 1-hour sample showed the same line still dominating the journal. No crash, no coredump — the app is otherwise usable.

    Why this may be worth generalising

    For this particular stale path, ENOENT never resolved — the directory stayed absent for the life of the process, so retrying could not succeed. ENOENT is not permanent in general (a directory may be created later), so the useful distinction is probably "has not resolved after N attempts" rather than "permanent by errno". Related reports show repeated Git-watcher failures in other environments, though not necessarily in the same watcher or code path: #33764, #29084, and #36961. My Linux observation is specifically git-init-watcher returning ENOENT. A fix that classifies non-transient fs.watch() errors and stops retrying — rather than special-casing WSL or SMB paths — would cover all of them.

    On a laptop the cost is a wakeup every 5s for the lifetime of the process, plus journal spam that buries other diagnostics.

  5. stephanelepain-hub commented on Aug 16, 2026

    @stephanelepain-hub

    Superseded — see my later comment. The config.toml entry below turned out not to be the source: removing it did not stop the loop. The path is restored from persisted session state. The rest of this comment is kept for the record.

    Correction to my earlier comment — I said I couldn't find where the stale workspace root came from and guessed at synced account state. That was wrong: I searched the wrong directory. It is plain local config.

    The path is a project entry in ~/.codex/config.toml:

    [projects."/home/<user>/git/<project>"]
    trust_level = "trusted"

    That directory does not exist on this host, and git-init-watcher retries fs.watch() against it every ~5s for the life of the process.

    Local reproduction (no account state, no WSL/SMB needed)

    1. Quit the app.
    2. Add a [projects."<absolute path that does not exist>"] entry to ~/.codex/config.toml (or add a workspace, then delete the directory).
    3. Start the app and leave it idle.
    4. journalctl --user -f | grep git-init-watcher

    The ENOENT line repeats indefinitely, no backoff, no give-up.

    Superseded — see my later comment. The config.toml entry turned out not to be the source: removing it did not stop the loop. The path is restored from persisted session state. I have only tested Linux, so I can't say how this behaves on macOS or Windows — which fits this being one watcher bug across platforms (ENOENT here, EISDIR on WSL in the original report) rather than several platform-specific ones.

  6. stephanelepain-hub commented on Aug 16, 2026

    @stephanelepain-hub

    Trigger isolated on Linux: the stale path survived every configuration-level removal I could find.

    Same host as my earlier comments (Fedora 44, chatgpt-26.810.52044-1.x86_64, Wayland). Following the ENOENT loop back, the missing workspace root kept returning after every config-level removal. The evidence points at historical rollout files as the source, though I have not read the code and cannot confirm the mechanism.

    Chain of custody

    Three rollout files dated 2026-05-01:

    ~/.codex/sessions/2026/05/01/rollout-2026-05-01T14-14-03-<uuid>.jsonl
    

    each containing:

    "cwd":"/home/<user>/git/<project>"

    That directory no longer exists. After a restart the rows were observed recreated in local_thread_catalog; state_5.sqlite was separately observed to hold the same path, though I did not watch it being repopulated. The two stores were local_thread_catalog in ~/.codex/sqlite/codex-dev.db, and threads in ~/.codex/state_5.sqlite. The thread IDs in local_thread_catalog match the rollout filenames exactly; I did not perform the same check against state_5.sqlite.

    Why config-level fixes do not work

    Verified in this order, measuring after each step:

    1. Removed the [projects."…"] entry from config.toml → thread persisted.
    2. Launched with cwd /tmp and zero [projects] entries → thread persisted.
    3. Deleted the rows from local_thread_catalog → rows were recreated on next start, and the thread came back heavier than before.
    4. Moved the three rollout files aside and cleared both derived tables → thread went idle and stayed idle across a restart.

    Only step 4 held — but note it changed three things at once (moved the rollouts and cleared both stores), so it does not isolate which of them was authoritative. What it does establish is that no configuration-level change I tried was sufficient; clearing persisted state was required.

    Measured cost (instantaneous /proc sampling, not ps lifetime averages)

    state app CPU RSS Tctl
    as installed, settled idle 30.0% 3417 MB 64.9 °C
    as installed, peak observed 41.8% 3437 MB 74.8 °C
    after moving rollouts aside and clearing both stores 2.0% 2328 MB 58.0 °C

    A thread named git inside the main process accounted for 99 of 121 ticks (82% of the main process's work) in one sample, and 242 ticks in a later one. git-init-watcher log lines went from ~55 per 30 min to 0. Across the full mitigation sequence, settled Tctl fell from 64.9 °C to 58.0 °C; the pre-change peak was 74.8 °C. These measurements do not isolate the temperature contribution of each change.

    Observed effects associated with the same stale path

    • The behaviour is consistent with there being no staleness check when the thread index is rebuilt — inferred from observation, not from reading the code. Either way, a cwd that no longer exists should be skipped rather than watched. (I measured CPU in a thread named git and the watcher's log output; I did not instrument what it was reading, so "scanning" would be my assumption rather than a measurement.)
    • The retry loop already described in this issue is the visible symptom; the CPU burn is the larger cost and is easy to miss because the app remains responsive.

    Unrelated but found alongside — the Linux app launches its GPU process with --ozone-platform=x11 on a Wayland session, Under X11, both GetVSyncParametersIfAvailable() failed and Unable to get gpu adapter appeared in the journal and the GPU process measured 17.7% CPU on an idle window. Under Wayland, it measured 6.5%, and both error classes were absent after restart. Happy to open that separately if it is out of scope here.


    Edited after posting for accuracy. Two changes worth flagging: (a) the measurement table originally showed a single "as installed" row of 41.8% / 74.8 °C, which was the peak sample rather than the settled idle baseline and overstated the improvement — both pre-cleanup samples are now shown separately; (b) several statements about the app's internals were phrased as fact when they are inferred from observed behaviour, and have been reworded. I have not read the source. The measurements themselves are unchanged.

    Further edit: narrowed several statements that outran the evidence — which store was observed being recreated versus merely holding the path, which table had its IDs cross-checked, "scanning" (not instrumented), "free-running" (an inference), and the fact that the final step was a compound intervention rather than an isolated one.

  7. yunyunFlow commented on Aug 29, 2026

    @yunyunFlow

    Update from Codex Desktop 26.825.4187.0 on Windows 11 with WSL2/Ubuntu 24.04: the issue persists.

    • One live sample recorded 299 git-init-watcher/EISDIR failures in 1,312 seconds, settling at roughly one retry every five seconds.
    • A related path bug persists: after exact offline cwd correction and successful JSON/SQLite integrity checks, Desktop restart regenerates malformed values such as <PACKAGE_ROOT>\app\resources\C:\... or <PACKAGE_ROOT>\app\resources\\wsl$\.... The saved WSL project root itself remains valid.
    • One affected session also ended in Windows MoAppHang after repeated Chromium GPU-process crash/relaunch failures. The watcher was active concurrently; this does not establish causation.

    No OOM, WSL crash, disk exhaustion, task-history/database corruption, workspace modification, or data loss was found.

    Please normalize WSL cwd paths, never prefix absolute paths with the package resource directory, stop/back off after deterministic watcher failures, and expose a supported watcher-disable setting.

    All user, host, task, and project identifiers are removed; no raw logs are attached.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingperformancewindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions