Hooks in --remote mode receive the daemon's cwd, not the session's
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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