Environment
- context-mode 1.0.169 (Codex plugin)
- codex-cli 0.157.1
- Windows 11
What happens
Once the most recently started Codex session on the machine is more than 5 minutes old, ctx_execute_file refuses files inside the workspace with File access blocked: "..." resolves outside the project root. The root it reports is the plugin install folder, C:\Users\<user>\.codex\plugins\cache\context-mode\context-mode\1.0.169. From the code path below, the same call should succeed inside that 5-minute window. I have not tested that directly.
Cause
Codex sets no workspace env var, so getProjectDir() (src/server.ts) uses resolveCodexSessionCwd (src/util/project-dir.ts) with transcriptMaxAgeMs: 5 * 60 * 1000. That function takes the rollout with the newest mtime under ~/.codex/sessions/ and returns null if that mtime is older than 5 minutes.
On Windows, Codex does not seem to update a rollout's mtime while it appends. On my machine, 37 of the 40 newest rollouts have an mtime equal to their first entry's timestamp. The other 3 match their last entry, which suggests the mtime updates only when the file is closed. One live rollout had an mtime about 20 minutes older than its last entry. So 5 minutes after a session starts, the resolver returns null and falls through to PWD, then to process.cwd().
start.mjs changes directory into its own folder before loading the server, so process.cwd() is the plugin root. .codex-plugin/mcp.json also spawns the server with "cwd": ".", so the original cwd is the plugin path too, and start.mjs correctly refuses to set CONTEXT_MODE_PROJECT_DIR from it. Nothing else supplies the workspace.
Why dropping the age guard is not enough
Discovery uses the newest rollout across all sessions. Without the guard, a Codex session started later in another directory (a git worktree, say) would become the allowed root for every other running Codex session.
Possible fixes
- MCP roots: after
initialize, if the client advertises the roots capability, call roots/list and use the result. That is per session and defined by the protocol. I have not verified whether Codex answers roots/list.
- Resolve once per server: Codex spawns one server per session. On my machine, each session's rollout was created within a second of its server process starting, sometimes just after it (0.6 s later in one case). So a lookup at boot can miss it. Instead, resolve on the first tool call (or retry briefly after start), using the rollout created within a few seconds of the process start time, and cache the result for the life of the process. If more than one rollout falls in that window, return null rather than guess. This also removes the per-call "newest rollout anywhere" lookup.
Acceptance check
Start Codex session A in directory X. After more than 6 minutes, start session B in directory Y.
- In A,
ctx_execute_file on a file in X succeeds, and on a file in Y it is refused.
- In B, a file in Y succeeds, and a file in X is refused.
Environment
What happens
Once the most recently started Codex session on the machine is more than 5 minutes old,
ctx_execute_filerefuses files inside the workspace withFile access blocked: "..." resolves outside the project root. The root it reports is the plugin install folder,C:\Users\<user>\.codex\plugins\cache\context-mode\context-mode\1.0.169. From the code path below, the same call should succeed inside that 5-minute window. I have not tested that directly.Cause
Codex sets no workspace env var, so
getProjectDir()(src/server.ts) usesresolveCodexSessionCwd(src/util/project-dir.ts) withtranscriptMaxAgeMs: 5 * 60 * 1000. That function takes the rollout with the newest mtime under~/.codex/sessions/and returns null if that mtime is older than 5 minutes.On Windows, Codex does not seem to update a rollout's mtime while it appends. On my machine, 37 of the 40 newest rollouts have an mtime equal to their first entry's timestamp. The other 3 match their last entry, which suggests the mtime updates only when the file is closed. One live rollout had an mtime about 20 minutes older than its last entry. So 5 minutes after a session starts, the resolver returns null and falls through to
PWD, then toprocess.cwd().start.mjschanges directory into its own folder before loading the server, soprocess.cwd()is the plugin root..codex-plugin/mcp.jsonalso spawns the server with"cwd": ".", so the original cwd is the plugin path too, andstart.mjscorrectly refuses to setCONTEXT_MODE_PROJECT_DIRfrom it. Nothing else supplies the workspace.Why dropping the age guard is not enough
Discovery uses the newest rollout across all sessions. Without the guard, a Codex session started later in another directory (a git worktree, say) would become the allowed root for every other running Codex session.
Possible fixes
initialize, if the client advertises therootscapability, callroots/listand use the result. That is per session and defined by the protocol. I have not verified whether Codex answersroots/list.Acceptance check
Start Codex session A in directory X. After more than 6 minutes, start session B in directory Y.
ctx_execute_fileon a file in X succeeds, and on a file in Y it is refused.