Remote connections can mix project/thread host state when SSH hosts share HOME

Open 💬 1 comment Opened Apr 30, 2026 by VishalJ99

Summary

Codex Desktop remote connections can mix up remote projects and threads when multiple SSH hosts share the same remote $HOME directory.

In this setup, a project can appear under one remote machine in the sidebar, while opening a thread from that project resumes against another machine. The visible symptom is a remote-host mismatch plus Current working directory missing, even though SSH connectivity and the remote Codex app servers are otherwise healthy.

What Happened

I configured multiple SSH remotes in Codex Desktop and opened the same remote project path on different hosts in an HPC/research-style environment.

The app entered a mixed state:

  • The sidebar showed the project under the expected remote host.
  • Opening an existing thread showed a different remote host in the bottom-right connection badge.
  • The composer showed Current working directory missing / This chat's working directory no longer exists.
  • Follow-up turns could not proceed normally because the thread was effectively attached to the wrong host/context.

This was not caused by SSH auth failure or a stale remote Codex binary:

  • Both SSH hosts were reachable.
  • Both codex app-server processes could run.
  • Updating the remote Codex CLI and restarting stale app-server processes fixed version/model-list issues, but not this project/thread host mismatch.

Who This Affects

This likely affects users on systems where multiple SSH hosts share the same network home directory, including:

  • university/research clusters;
  • GPU/login/compute node fleets;
  • enterprise Linux environments;
  • servers using NFS, AFS, Lustre, or similar shared home mounts.

This is a common pattern in exactly the kinds of remote-first environments where Codex Desktop remote connections are most useful.

Why It Happens

The remote Codex app server appears to use $HOME/.codex as its state root unless CODEX_HOME is explicitly set.

On shared-home systems, different SSH hosts can resolve to distinct machines but still share the same $HOME, so all of these files are shared across hosts:

$HOME/.codex/history.jsonl
$HOME/.codex/session_index.jsonl
$HOME/.codex/state_5.sqlite
$HOME/.codex/archived_sessions/...

That means sessions created or indexed from host A are visible to host B. The desktop UI can then group a project by one host/path while the opened conversation still carries or resolves to another host binding.

A diagnostic that exposed the issue was checking both hosts:

ssh <host-a> 'hostname -f; realpath ~/.codex; df -hT "$HOME"; ls -lh ~/.codex/session_index.jsonl ~/.codex/state_5.sqlite'
ssh <host-b> 'hostname -f; realpath ~/.codex; df -hT "$HOME"; ls -lh ~/.codex/session_index.jsonl ~/.codex/state_5.sqlite'

In the failing case, both hosts reported the same shared home filesystem and the same .codex state files.

Potential Product Remedy

Codex Desktop should not assume that ~/.codex is machine-local when launching or connecting to a remote app server.

A robust fix would be to namespace mutable remote state by remote connection identity, for example by launching the remote app server with:

CODEX_HOME="$HOME/.codex/remotes/<stable-remote-connection-id>"

where <stable-remote-connection-id> is derived from the configured SSH connection identity, such as the connection UUID, user, host alias/resolved hostname, and port.

Important details:

  • The namespace should be per remote connection, not just per project path or folder label.
  • Project/thread keys should include both hostId and remotePath.
  • Thread resume should validate that the thread's stored host matches the currently selected/attached host.
  • If a mismatch is detected, the UI should show a clear message like This thread belongs to <host> instead of opening it under a different project row.
  • Auth/config/plugin state may need a migration/copy/symlink strategy so users do not need to re-authenticate separately for every namespaced remote state directory.

A smaller partial fix would be to keep global config/auth shared but split mutable runtime/session stores by remote host:

history.jsonl
session_index.jsonl
state_5.sqlite
archived_sessions/
logs/
shell_snapshots/

Local Hotfix

As a workaround, I set a host-specific CODEX_HOME in the remote shell startup before codex app-server launches:

# In remote shell startup, e.g. ~/.bashrc
_codex_remote_host="$(hostname -s 2>/dev/null || hostname)"
case "$_codex_remote_host" in
  <host-a>|<host-b>|<host-c>)
    export CODEX_HOME="$HOME/.codex-hosts/$_codex_remote_host"
    mkdir -p "$CODEX_HOME" 2>/dev/null || true
    ;;
esac
unset _codex_remote_host

Then I seeded each host-specific directory with the shared config/auth files needed by the app server:

mkdir -p ~/.codex-hosts/<host>/rules ~/.codex-hosts/<host>/archived_sessions ~/.codex-hosts/<host>/log
cp -p ~/.codex/auth.json ~/.codex/config.toml ~/.codex/AGENTS.md ~/.codex-hosts/<host>/ 2>/dev/null || true
cp -a ~/.codex/rules/. ~/.codex-hosts/<host>/rules/ 2>/dev/null || true
touch ~/.codex-hosts/<host>/history.jsonl ~/.codex-hosts/<host>/session_index.jsonl

After restarting the remote app servers, I verified each process had a distinct CODEX_HOME:

tr '\0' '\n' < /proc/<codex-app-server-pid>/environ | grep '^CODEX_HOME='

Expected:

host A: CODEX_HOME=$HOME/.codex-hosts/<host-a>
host B: CODEX_HOME=$HOME/.codex-hosts/<host-b>

New remote chats then used the correct machine-local state. Existing broken threads may still carry stale host metadata, so the reliable validation path is to create a fresh thread after splitting the state directories.

Expected Behavior

Remote project and thread state should be scoped to the selected remote host/connection. A thread created on host A should not appear resumable as if it belonged to host B solely because both machines share $HOME/.codex.

Actual Behavior

With shared $HOME, host A and host B share Codex remote state. This can cause the sidebar project host, opened thread host, and working directory to disagree.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗