Skip to content

app-server thread/resume rejects equivalent symlinked rollout paths as stale #21898

Open

Description

@SDGLBL

What issue are you seeing?

When using the experimental app-server API as a third-party client, thread/resume can reject a running thread if the requested rollout path and the active rollout path refer to the same file through different symlink spellings.

The failure happens in the running-thread resume path when params.path is provided. The current check compares the two PathBuf values literally:

requested_path != active_path

On machines where the home directory is a symlink, one path can use the symlinked home while the other uses the resolved real path. These paths point to the same rollout JSONL, but app-server treats them as different and returns:

cannot resume running thread <thread-id> with stale path: requested `<symlink-home>/.../rollout-<id>.jsonl`, active `<real-home>/.../rollout-<id>.jsonl`

This blocks third-party clients that rely on experimental thread/resume with path.

What steps can reproduce the bug?

  1. Run Codex app-server with experimental API enabled.
  2. Use an environment where the user home directory is a symlink, for example:
    • /home/user resolves to /mnt/users/user
    • or /Users/user resolves through another mounted path
  3. Start or resume a thread so app-server has a running thread with an active rollout path.
  4. Call thread/resume for that same running thread with params.path set to the rollout JSONL path using the symlinked home spelling.
  5. Observe that app-server compares the requested path and active rollout path literally and rejects the request as stale path, even though both paths resolve to the same file.

A downstream workaround was to temporarily disable this stale-path check, but the proper upstream fix should preserve the stale-path protection while comparing normalized/canonicalized paths, or otherwise using a path identity check that treats equivalent symlinked paths as the same file.

What is the expected behavior?

thread/resume should allow resuming a running thread when params.path and the active rollout path resolve to the same file.

It should still reject genuinely stale paths that point to a different rollout/thread.

Additional information

This appears related to the broader class of path identity issues reported in #18483, #20317, and #20882, but the trigger here is narrower: the app-server v2 experimental thread/resume running-thread path check rejects equivalent symlinked paths before the client can resume.

Environment observed downstream:

  • Codex CLI/app-server version: 0.130.0
  • Platform: Darwin 24.6.0 arm64 arm
  • Scenario: third-party app-server client, experimental API enabled
  • Home directory is symlinked on internal remote machines

Activity

  1. added
    bugSomething isn't working
    app-serverIssues involving app server protocol or interfaces
    on May 9, 2026
  2. bdteo commented on May 20, 2026

    @bdteo

    Adding another reproduction from Codex Desktop on macOS, because this is also breaking first-party heartbeat automations, not only third-party app-server clients.

    Environment:

    • Codex Desktop / CLI: 0.131.0-alpha.9
    • Platform: macOS / Darwin, Apple Silicon
    • ~/.codex is a symlink:
      • /Users/boris/.codex -> /Users/boris/DevEnvs/Boris/MacShellUtils/Users/Boris/codex

    Failure observed from a heartbeat automation targeting the current local thread:

    cannot resume running thread 019e40d5-806b-7d91-930a-6c0b59e796ed with stale path: requested `/Users/boris/.codex/sessions/2026/05/19/rollout-2026-05-19T18-23-14-019e40d5-806b-7d91-930a-6c0b59e796ed.jsonl`, active `/Users/boris/DevEnvs/Boris/MacShellUtils/Users/Boris/codex/sessions/2026/05/19/rollout-2026-05-19T18-23-14-019e40d5-806b-7d91-930a-6c0b59e796ed.jsonl`
    

    Both paths are the same rollout JSONL via symlink vs canonical path. The heartbeat automation TOML itself was normal and only stored the target_thread_id; the Desktop automation scheduler read the thread, called resumeThread(id, { cwd, path }), and app-server rejected it on raw path inequality.

    Impact:

    • Heartbeat automations become unusable for symlinked / repo-backed Codex homes.
    • The automation can be created and updated successfully, so the failure only appears later when the scheduler tries to resume the target thread.
    • A local launchd monitor was needed as a workaround for a production runbook because the native heartbeat could not start.

    Expected behavior is exactly as described above: keep the stale-path protection, but compare canonicalized paths or filesystem identity so symlink/canonical forms of the same JSONL are accepted.

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

    app-serverIssues involving app server protocol or interfacesbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions