openai / openai/codex

Hooks in --remote mode receive the daemon's cwd, not the session's

Open
#42,457 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app-server bug CLI hooks remote
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Summary

In --remote mode the session runs inside the app-server daemon, and plugin hooks are handed the daemon's working directory as cwd instead of the directory the client was launched in. The App Server's own thread/list files the thread under that same directory, so there is no path by which a plugin can learn which project the session actually belongs to.

To be clear about what is not happening: hooks do fire in remote mode, with correct event names and a well-formed payload. Only the reported directory is wrong.

Environment
  • codex-cli 0.152.1 (managed standalone install), app-server 0.152.1
  • macOS 26.6.2, darwin arm64
Reproduction

1. A plugin whose hook just dumps its payload.

~/.agents/repro/plugins/repro/hooks.json:

{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command",
                     "command": "sh \"$HOME/.agents/repro/plugins/repro/dump.sh\"" } ] }
    ],
    "UserPromptSubmit": [
      { "hooks": [ { "type": "command",
                     "command": "sh \"$HOME/.agents/repro/plugins/repro/dump.sh\"" } ] }
    ]
  }
}

~/.agents/repro/plugins/repro/dump.sh:

#!/bin/sh
{ cat; echo; } >> /tmp/codex-hook-payload.log

Register and enable it in ~/.codex/config.toml:

[marketplaces.repro]
source_type = "local"
source = "/Users/<you>/.agents/repro"

[plugins."repro@repro"]
enabled = true

2. Start the daemon from directory A, then start a session from directory B:

cd /path/A && codex app-server daemon start
cd /path/B && codex --remote unix://

Send one prompt, then read /tmp/codex-hook-payload.log.

Observed

Remote session, client launched in /path/B:

{"session_id":"01a0658f-...","turn_id":"01a06598-...","transcript_path":"...",
 "cwd":"/path/A","hook_event_name":"UserPromptSubmit","model":"...",
 "permission_mode":"default","prompt":"test2"}

cwd is /path/A - the daemon's directory.

Same plugin, same machine, local session launched in /path/B (codex exec, no --remote):

{"session_id":"01a06592-...","transcript_path":"...","cwd":"/path/B",
 "hook_event_name":"SessionStart","model":"...","permission_mode":"default",
 "source":"startup"}

cwd is /path/B - correct. So the discrepancy is specific to remote mode.

The App Server records the same wrong directory

Querying the control socket (JSON-RPC over WebSocket on ~/.codex/app-server-control/app-server-control.sock) for that same live thread:

initialize
thread/list { "useStateDbOnly": true, "cwd": "/path/B" }
  -> { "data": [] }
thread/list { "useStateDbOnly": true, "cwd": "/path/A" }
  -> { "data": [ { "id": "01a0658f-...", "cwd": "/path/A" }, ... ] }

The live thread is filed under the daemon's directory as well, so the client's directory is not retrievable from the App Server either.

Restarting the daemon does not change the behaviour - this is not a stale-config problem.

Expected

cwd in the hook payload, and the cwd recorded for the thread, are the directory the client was launched in (/path/B).

Impact

Any plugin that treats cwd as project identity - per-project state, workspace-scoped coordination, per-repo settings - attributes a remote session to whichever directory the daemon happened to be started in. Because the daemon is long-lived and started once, in practice every remote session collapses onto that single directory.

Where such a plugin keeps state per directory, this is not only a mis-attribution: a session working in project B can be shown, and addressed with, state belonging to project A.

This was found while integrating a third-party plugin that uses cwd for workspace identity; the mis-attribution is silent, since nothing in the payload indicates the directory is a substitute.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the --remote app-server daemon entry point and trace how the client's launch directory reaches thread/list and the hook payload. Reproduce with the daemon in /path/A and the client in /path/B, then verify that both the recorded thread cwd and hook cwd use /path/B while local mode remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.