@minhspark/codex-mcp-bridge 1.22.3 → 1.22.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-mcp-bridge",
3
- "version": "1.22.3",
3
+ "version": "1.22.4",
4
4
  "description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
5
5
  "author": {
6
6
  "name": "Bui Dang Minh"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](ht
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.22.4] - 2026-10-19
8
+
9
+ ### Fixed
10
+
11
+ - Discover moved Claude project directories for authorized new Desktop conversations using the workspace resolver and shared project policy. Report candidates separately from live sessions and resumable tasks; preserve original task IDs, account checks, directory bindings, revocations and exact new-session verification.
12
+ - Reject ambiguous workspace replacements during Claude project discovery and recheck the saved project before opening, trusting and submitting, including restored requests. Preserve original-path revocations across ancestor junction changes.
13
+ - Retain creation scope bindings when an error occurs after opening the native composer, so the original request remains inspectable without reopening or resending.
14
+
15
+ ### Verification
16
+
17
+ - CI passed on Windows, macOS and Linux with Node 22 and 24, including project revocation, source replacement, restored-request and receipt-binding regressions.
18
+ - A live Windows Desktop peer message received its correlated Claude reply, and the reverse Claude message arrived in the originating Codex chat with the same nonce. Account, process, task, project and permission checks remained enabled.
19
+
20
+ ### Upgrade notes
21
+
22
+ - Existing supervisors load updated source when safe. Relocation discovery requires the shared project policy and does not make a dormant or moved task a verified live recipient.
23
+ - Peer sender attribution requires a verified live Claude Code task. Native composer creation remains a separate operation; it does not substitute for a peer message or its native sender label. This release does not establish Cowork or Dispatch connectivity.
24
+
7
25
  ## [1.22.3] - 2026-10-15
8
26
 
9
27
  ### Fixed
package/README.md CHANGED
@@ -4,8 +4,6 @@
4
4
 
5
5
  <h1 align="center">codex-mcp-bridge</h1>
6
6
 
7
- Local Desktop handoff guides: [daily workflow and directory verification](docs/daily-handoffs.md), [Windows setup](docs/new-windows-setup.md), [macOS setup](docs/new-macos-setup.md), and [project onboarding](docs/project-onboarding.md).
8
-
9
7
  <p align="center">
10
8
  <a href="https://www.npmjs.com/package/@minhspark/codex-mcp-bridge"><img src="https://img.shields.io/npm/v/@minhspark/codex-mcp-bridge?logo=npm&amp;color=CB3837" height="20" alt="npm" /></a>
11
9
  <a href="https://www.npmjs.com/package/@minhspark/codex-mcp-bridge"><img src="https://raw.githubusercontent.com/buidangminh23/codex-mcp-bridge/analytics/stats-npm-downloads.svg" height="20" alt="npm downloads" /></a>
@@ -21,117 +19,23 @@ Send prompts and replies between **Claude and Codex**, keeping each conversation
21
19
 
22
20
  https://github.com/user-attachments/assets/98b23989-826f-4d7d-9dcb-ad7dd2739095
23
21
 
24
- ## Release notifications
25
-
26
- To receive new release notifications, open [this repository](https://github.com/buidangminh23/codex-mcp-bridge), select **Watch → Custom → Releases**, then click **Apply**. Choose GitHub or email delivery in your [notification settings](https://github.com/settings/notifications).
27
-
28
- Starring the repository or downloading/installing a package does not subscribe you to release notifications. Notifications do not update your installed copy; follow the installation instructions to update.
29
-
30
- [View release notes](https://github.com/buidangminh23/codex-mcp-bridge/releases).
31
-
32
22
  ## Installation
33
23
 
34
- ### ChatGPT plugin
35
-
36
- [Open Codex Bridge](https://codex-mcp-bridge.buidangminh23.chatgpt.site) to connect ChatGPT to **your own computer**. Each ChatGPT account pairs its own connector and can access only the local project directories selected during setup. Native Desktop mode supports Windows and macOS. Keep Codex Desktop and the connector running.
37
-
38
- Install Node.js 22+, sign in to Codex Desktop and Claude Desktop, and save the intended project in Codex Desktop. The connector pins both local account identities and stops if either account changes. Install the native relay using the platform instructions below, then pair:
39
-
40
- Windows PowerShell:
41
-
42
- ```powershell
43
- npm.cmd install -g @minhspark/codex-mcp-bridge@latest
44
- codex-native-relay-install.cmd --desktop-tasks
45
- codex-sites-connector.cmd --pair --site https://codex-mcp-bridge.buidangminh23.chatgpt.site --roots "C:\Projects\YourProject"
46
- ```
47
-
48
- macOS Terminal:
49
-
50
24
  ```bash
51
25
  npm install -g @minhspark/codex-mcp-bridge@latest
52
- codex-native-relay-install --desktop-tasks
53
- codex-sites-connector --pair --site https://codex-mcp-bridge.buidangminh23.chatgpt.site --roots "$HOME/YourProject"
54
- ```
55
-
56
- Open the pairing URL printed in the terminal, sign in with the ChatGPT account that will use the plugin, and choose **Connect this computer**. The Site owner can install its provisioned plugin from **Plugins → Personal → Created by you**. To restart an already paired connector, run `codex-sites-connector` (`codex-sites-connector.cmd` on Windows). To replace the paired computer, use **Disconnect existing computer** on a fresh pairing page first.
57
-
58
- The public MCP endpoint is `https://codex-mcp-bridge.buidangminh23.chatgpt.site/mcp`; it uses Sign in with ChatGPT. A plugin compatibility ZIP is attached to the [1.21.0 release](https://github.com/buidangminh23/codex-mcp-bridge/releases/tag/v1.21.0) for manual import in clients that support plugin packages. ChatGPT personal accounts cannot share their Sites-provisioned plugin directly by invitation or share link. A public directory listing requires verified developer identity and OpenAI review; this release does not claim directory approval. See [OpenAI's Sites plugin access rules](https://help.openai.com/en/articles/20001547-hosting-a-plugin-with-chatgpt-sites).
59
-
60
- Use a semicolon between multiple Windows roots or a colon on macOS. The relay's executor must belong to an allowed project. Credentials and operation receipts stay in the current OS user's private `~/.codex/sites-bridge` directory. The connector uses outbound HTTPS; no public port or tunnel is required.
61
-
62
- The plugin can list projects, read conversations, create an authorized task, and continue an existing task. Requests return an operation ID; read its result before sending more work. An uncertain send is never automatically repeated. Disconnecting blocks future dispatch and cancels queued work; tasks already sent to Desktop keep running. Linux/WSL users can use the existing CLI bridge below; the hosted connector requires native Codex Desktop.
63
-
64
- The hosted Worker source and database migrations are in `sites/codex-bridge`. The portable `plugin.json`, `mcp.json`, compatibility manifest, and workflow skill are included in the npm package and this repository. The plugin ZIP has its portable manifest directly at the archive root.
65
-
66
- #### Hosted data
67
-
68
- The hosted service is operated by Bui Dang Minh and runs on OpenAI Sites. Sign in with ChatGPT supplies a Site-scoped user identifier; the Worker uses it to bind a connector and isolate each account's operation records. The Worker does not use profile names or email addresses. OpenAI handles sign-in and hosting independently.
69
-
70
- The database stores the account identifier, a connector identifier and token hash, connection timestamps, requested tool arguments (including task prompts), and the returned Desktop results. These records support polling and duplicate-send prevention. The service operator can access this database. Data is sent only to the paired computer and returned to the requesting account through the authenticated service. A local connector stores its credentials, account fingerprints and operation receipts in the OS user's private configuration directory.
71
-
72
- Requests expire for dispatch after two minutes. Expiry is not data deletion: operation records remain stored to preserve dispatch history. Disconnecting revokes the device token and cancels undispatched work; it does not erase operation history or stop tasks already running in Desktop. For deletion or privacy requests, contact the operator through the repository's support link, without posting private prompts, account credentials or conversation content in a public issue. No advertising or analytics collection is implemented by this hosted Worker.
73
-
74
- ### GitHub Packages
75
-
76
- The repository-linked copy is [@buidangminh23/codex-mcp-bridge](https://github.com/buidangminh23/codex-mcp-bridge/packages)
77
- on npm.pkg.github.com. The npmjs.com package remains @minhspark/codex-mcp-bridge.
78
- GitHub's npm registry requires authentication with a classic token with read:packages,
79
- even for public packages. Authenticate locally and never commit a token:
80
-
81
- ```bash
82
- npm login --scope=@buidangminh23 --registry=https://npm.pkg.github.com --auth-type=legacy
83
- npm install -g @buidangminh23/codex-mcp-bridge --registry=https://npm.pkg.github.com
84
- ```
85
-
86
- Then follow the platform registration and verification instructions below.
87
-
88
- [Windows](#windows-powershell) · [macOS](#macos-terminal) · [Linux / WSL](#linux--wsl-bash) · [Claude Code registration](#register-claude-code) · [Verify](#verify-the-installation) · [Troubleshooting](#troubleshooting)
89
-
90
- Choose the mode for the conversations you want to connect:
91
-
92
- | Platform | Mode | Required clients |
93
- |---|---|---|
94
- | Windows / macOS | Native Desktop tasks | Signed-in Codex Desktop and a Claude **Code** session in Claude Desktop |
95
- | Linux / WSL | CLI / app-server | Signed-in Codex CLI and a running Claude Code CLI session |
96
-
97
- The bridge requires **Node.js 22+**; Node 24 LTS is a suitable starting point. If Node is already managed by a version manager, use that installation. Install the bridge under the same OS user as the clients. A global npm install does not require cloning this repository.
98
-
99
- To opt a machine you administer into **Full access + Never**, run `codex-mcp-bridge-install --full-access` when registering the bridge (`codex-mcp-bridge-install.cmd --full-access` in PowerShell). The installer sets global Codex defaults for old and new projects, repairs or creates the managed policy with both `read-only` and `danger-full-access`, and records the choice so the bridge can restore it if the files drift later. Windows UAC, macOS administrator authentication, or Linux polkit/sudo may be required for the system policy. Normal installation does not change Codex permissions. Run `codex doctor --summary --ascii` after setup; existing Desktop tasks may need to be reopened to load the new permissions.
100
-
101
- If the bridge is already registered, `codex-full-access` (`codex-full-access.cmd` in PowerShell) enables the same settings without replacing that registration. The bridge reads the saved choice when it starts.
102
-
103
- For Desktop mode, install [Codex Desktop](https://developers.openai.com/codex/app) and [Claude Desktop](https://claude.com/download), sign in, and save the intended local project in Codex Desktop. Open that same directory in Claude Desktop's Code tab. A normal Claude chat is not a Code session.
104
-
105
- #### Native relay pipe forwarding
106
-
107
- After running the native relay installer, add this setting to its **existing** entry in `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`):
108
-
109
- ```toml
110
- [mcp_servers.codex-native-relay]
111
- env_vars = ["CODEX_APP_TOOLS_PIPE_PATH"]
112
26
  ```
113
27
 
114
- Keep the entry's `command`, `args`, and other settings; append the variable name if `env_vars` already contains names. Use your configured server name if you set `CODEX_NATIVE_RELAY_NAME`. This forwards the running Desktop app-server's pipe path without saving its temporary value. The installer uses `codex mcp add`, which does not provide an `env_vars` option, and refuses to reset entries with custom transport settings, including `env_vars`.
28
+ Requires **Node.js 22+**. In Windows PowerShell, use `npm.cmd` instead of `npm`. Run the same command to update; reconnect MCP servers if their settings or installation paths changed.
115
29
 
116
- Reconnect `codex-native-relay` in the existing Desktop task, or restart Codex Desktop after active work finishes. Verify `native_relay_status` from that task. Without forwarding, Desktop setups whose pipe is absent from app-server command-line configuration can expose status tools while reporting native pipe unavailable and relay sockets not listening; a connected Claude bridge alone does not establish native delivery.
30
+ [Windows](#windows-powershell) · [macOS](#macos-terminal) · [Linux / WSL](#linux--wsl-bash) · [ChatGPT plugin](#chatgpt-plugin)
117
31
 
118
32
  ### Windows (PowerShell)
119
33
 
120
- Install Node and a native Codex executable with WinGet:
34
+ Install and sign in to [Codex Desktop](https://developers.openai.com/codex/app) and [Claude Desktop](https://claude.com/download). Save the project in Codex Desktop and open the same directory in Claude Desktop's **Code tab**. Have the Codex and Claude Code CLIs available.
121
35
 
122
- ```powershell
123
- winget install --id OpenJS.NodeJS.LTS --exact
124
- winget install --id OpenAI.Codex --exact
125
- ```
126
-
127
- Open a **new PowerShell window** so it receives the updated PATH, then run:
36
+ If the Codex CLI is missing, run `winget install --id OpenAI.Codex --exact`, open a new terminal, then run `codex.exe login`.
128
37
 
129
38
  ```powershell
130
- node --version
131
- npm.cmd --version
132
- codex.exe --version
133
- codex.exe login
134
- npm.cmd install -g @minhspark/codex-mcp-bridge@latest
135
39
  $env:CODEX_EXE = (Get-Command codex.exe).Source
136
40
  codex-native-relay-install.cmd --desktop-tasks
137
41
  codex-mcp-bridge-install.cmd --desktop-tasks
@@ -139,107 +43,44 @@ $env:CODEX_BRIDGE_DESKTOP_TASKS = "1"
139
43
  claude-mcp-bridge-install.cmd
140
44
  ```
141
45
 
142
- The `.cmd` suffix selects npm's Windows launchers without changing PowerShell's execution policy. `CODEX_EXE` must point to the real `codex.exe`, not an npm `.ps1` shim. If WinGet is unavailable, install [App Installer](https://learn.microsoft.com/en-us/windows/package-manager/winget/) or use the vendors' installers.
143
-
144
- Continue with [Claude Code registration](#register-claude-code), then [verification](#verify-the-installation).
46
+ Next: [register Claude Code](#register-claude-code), add [relay pipe forwarding](#native-relay-pipe-forwarding), then [verify](#verify-the-installation).
145
47
 
146
48
  ### macOS (Terminal)
147
49
 
148
- With [Homebrew](https://brew.sh/) installed:
50
+ Install and sign in to Codex Desktop and Claude Desktop. Save the project in Codex Desktop and open the same directory in Claude Desktop's **Code tab**.
149
51
 
150
52
  ```bash
151
- brew install node@24
152
- export PATH="$(brew --prefix node@24)/bin:$PATH"
153
- node --version
154
- npm --version
155
- npm install -g @openai/codex@latest @minhspark/codex-mcp-bridge@latest
156
- codex --version
53
+ npm install -g @openai/codex@latest
157
54
  codex login
158
55
  codex-native-relay-install --desktop-tasks
159
56
  codex-mcp-bridge-install --desktop-tasks
160
57
  CODEX_BRIDGE_DESKTOP_TASKS=1 claude-mcp-bridge-install
161
58
  ```
162
59
 
163
- Add the same Node PATH line to `~/.zshrc` if this is your Node installation; use `~/.bashrc` for Bash. If Homebrew is not installed, the [Node.js installer](https://nodejs.org/en/download) is another option; skip the two Homebrew lines after installing it.
164
-
165
- Keep the bootstrap enabled on a first relay install: it creates the executor required by native delivery. `--no-bootstrap` is for an already configured executor. On macOS, the relay installer selects the runtime bundled with Codex Desktop for native app authentication.
166
-
167
- Continue with [Claude Code registration](#register-claude-code), then [verification](#verify-the-installation).
168
-
169
- ### Linux / WSL (Bash)
170
-
171
- Use a Linux terminal with `curl`, `unzip`, and Bash available. This example uses [fnm](https://github.com/Schniz/fnm#installation) to install Node without system-wide npm permissions:
172
-
173
- ```bash
174
- curl -fsSL https://fnm.vercel.app/install | bash
175
- ```
176
-
177
- Open a new Bash terminal so fnm's shell setup loads, then run:
178
-
179
- ```bash
180
- eval "$(fnm env --use-on-cd --shell bash)"
181
- fnm install 24
182
- fnm default 24
183
- fnm use 24
184
- node --version
185
- npm --version
186
- npm install -g @openai/codex@latest @minhspark/codex-mcp-bridge@latest
187
- codex login
188
- curl -fsSL https://claude.ai/install.sh | bash
189
- export PATH="$HOME/.local/bin:$PATH"
190
- claude --version
191
- CODEX_BRIDGE_DESKTOP_TASKS=0 claude-mcp-bridge-install
192
- ```
193
-
194
- Start `claude` once and complete sign-in, then exit back to the shell. Register the forward bridge below, then reopen Claude or reconnect its MCP server:
195
-
196
- ```bash
197
- bridge_root="$(npm root -g)/@minhspark/codex-mcp-bridge"
198
- claude mcp add --scope user codex-bridge \
199
- -e CODEX_BIN="$(command -v codex)" \
200
- -e CODEX_BRIDGE_DESKTOP_TASKS=0 \
201
- -e CODEX_BRIDGE_AUTOSTART=1 \
202
- -e CODEX_BRIDGE_THREAD_POLICY=roots \
203
- -e CODEX_BRIDGE_ALLOWED_ROOTS="$HOME" \
204
- -- "$(command -v node)" "$bridge_root/src/mcp-supervisor.mjs" index.mjs
205
- ```
206
-
207
- Use an existing writable project under your home directory, or replace `$HOME` in the allowed roots with the intended project directories. Keep both CLI clients running under the same Linux user. The bridge starts its local app-server on demand; no native relay installer is needed. This mode does not provide native Desktop project assignment. WSL and Windows have separate paths and client registrations; use the Windows instructions to connect Windows Desktop tasks.
60
+ Keep bootstrap enabled on the first relay install. Next: [register Claude Code](#register-claude-code), add [relay pipe forwarding](#native-relay-pipe-forwarding), then [verify](#verify-the-installation).
208
61
 
209
62
  ### Register Claude Code
210
63
 
211
- **Claude Desktop configuration and Claude Code's MCP registry are separate.** If the sending Code session does not have `codex-bridge`, register it below. These are first-registration commands; if `claude mcp get codex-bridge` already returns an entry, preserve its custom environment and access settings when updating it.
212
-
213
- Install the Claude Code CLI if `claude --version` is unavailable. The [official setup guide](https://code.claude.com/docs/en/setup) provides these native installers:
214
-
215
- **Windows PowerShell:**
216
-
217
- ```powershell
218
- irm https://claude.ai/install.ps1 | iex
219
- ```
220
-
221
- **macOS / Linux:**
222
-
223
- ```bash
224
- curl -fsSL https://claude.ai/install.sh | bash
225
- ```
64
+ Claude Desktop and Claude Code have separate MCP registrations. If `claude --version` is unavailable, [install Claude Code](https://code.claude.com/docs/en/setup) first. For a new registration, replace the example project path below with the actual directory. Preserve custom settings if `codex-bridge` is already registered.
226
65
 
227
- Open a new terminal after installation. Linux users who completed the preceding section are already registered. For **Windows Desktop mode**:
66
+ <details>
67
+ <summary>Windows PowerShell</summary>
228
68
 
229
69
  ```powershell
230
70
  $bridgeRoot = Join-Path ((npm.cmd root -g).Trim()) '@minhspark/codex-mcp-bridge'
231
- $nodeBin = (Get-Command node.exe).Source
232
- $codexBin = (Get-Command codex.exe).Source
233
71
  claude mcp add --scope user codex-bridge `
234
- -e "CODEX_BIN=$codexBin" `
72
+ -e "CODEX_BIN=$((Get-Command codex.exe).Source)" `
235
73
  -e CODEX_BRIDGE_DESKTOP_TASKS=1 `
236
74
  -e CODEX_BRIDGE_AUTOSTART=0 `
237
75
  -e CODEX_BRIDGE_THREAD_POLICY=roots `
238
- -e "CODEX_BRIDGE_ALLOWED_ROOTS=$env:USERPROFILE" `
239
- -- $nodeBin (Join-Path $bridgeRoot 'src/mcp-supervisor.mjs') index.mjs
76
+ -e "CODEX_BRIDGE_ALLOWED_ROOTS=C:\Projects\YourProject" `
77
+ -- (Get-Command node.exe).Source (Join-Path $bridgeRoot 'src/mcp-supervisor.mjs') index.mjs
240
78
  ```
241
79
 
242
- For **macOS Desktop mode**:
80
+ </details>
81
+
82
+ <details>
83
+ <summary>macOS Terminal</summary>
243
84
 
244
85
  ```bash
245
86
  bridge_root="$(npm root -g)/@minhspark/codex-mcp-bridge"
@@ -248,249 +89,105 @@ claude mcp add --scope user codex-bridge \
248
89
  -e CODEX_BRIDGE_DESKTOP_TASKS=1 \
249
90
  -e CODEX_BRIDGE_AUTOSTART=0 \
250
91
  -e CODEX_BRIDGE_THREAD_POLICY=roots \
251
- -e CODEX_BRIDGE_ALLOWED_ROOTS="$HOME" \
92
+ -e CODEX_BRIDGE_ALLOWED_ROOTS="$HOME/YourProject" \
252
93
  -- "$(command -v node)" "$bridge_root/src/mcp-supervisor.mjs" index.mjs
253
94
  ```
254
95
 
255
- These examples allow projects under the current user's home. For projects elsewhere, supply their actual absolute directories, separated by `;` on Windows or `:` on macOS/Linux. `--scope user` makes the registration available across projects; it does not override the allowed roots.
256
-
257
- ### Verify the installation
258
-
259
- For Desktop project onboarding, both MCP entry points expose `inspect_bridge_project` (read-only) and `prepare_bridge_project` (user-selected workspace trust plus shared messaging grant). Setup preserves unrelated settings, backs up changes, respects revocations, and resolves registered worktrees to their primary repository. It does not approve tools or claim live connectivity. `CODEX_BRIDGE_PROJECT_POLICY` must explicitly name the shared policy. See [project onboarding and independent optional native settings integration](docs/project-onboarding.md). `node scripts/bridge-projects.mjs register-card-settings` registers the optional card-extension native settings integration; the bridge never depends on that extension.
260
-
261
- Check registration from a terminal; on Windows use `codex.exe` if `codex` resolves to a blocked PowerShell shim:
262
-
263
- ```bash
264
- node --version
265
- codex --version
266
- claude --version
267
- claude mcp get codex-bridge
268
- codex mcp get claude-bridge
269
- ```
270
-
271
- For Windows/macOS Desktop mode, also run `codex mcp get codex-native-relay`. Reconnect the affected MCP servers in the **existing** client tasks; Claude Code exposes them through `/mcp`. If a client has no reconnect control, restart that client after its active work finishes.
272
-
273
- Ask the active tasks to run these **MCP tools**, not shell commands:
96
+ </details>
274
97
 
275
- | Where | Tool | Expected result |
276
- |---|---|---|
277
- | Claude Code | `codex_bridge_status` | Current runtime; native relay and saved projects available in Desktop mode, or a working app-server in CLI mode |
278
- | Codex | `claude_bridge_status` | Current runtime and the intended Claude session policy |
279
- | Codex Desktop only | `native_relay_status` | Account relay listening and native tools available |
280
-
281
- The registered supervisor should report auto-reload enabled. Next, list the intended destination with `list_codex_threads` or `list_claude_sessions`, then send a short message and verify its reply. A package version or a running process alone does not establish successful delivery.
282
-
283
- ## Use
284
-
285
- | Direction | Tools |
286
- |---|---|
287
- | Continue unfinished Codex work | Verify the original `threadId`, then `send_to_codex_thread` |
288
- | Receive a reply after a Desktop send times out | `wait_codex_reply` with the original `deliveryId`; repeat bounded waits without sending again |
289
- | Codex → Claude | `list_claude_sessions`, then `send_to_claude_session` |
290
- | Start independent Codex work | `delegate_to_codex` or `start_codex_thread` with `cwd`, initial `prompt`, and a fresh UUID `requestId` in Desktop mode |
98
+ Separate multiple allowed directories with `;` on Windows or `:` on macOS/Linux.
291
99
 
292
- Example requests:
100
+ ### Native relay pipe forwarding
293
101
 
294
- - **In Claude:** “For each independent new task or feature, create a new Codex conversation and send the initial brief there. Continue unfinished work in its original conversation.”
295
- - **In Claude:** “Send this review request to my existing Codex task in this project and wait for its reply.”
296
- - **In Codex:** “Send this result to my Claude Desktop Code session in this project and confirm its reply.”
102
+ Add this to the relay's **existing** entry in `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`), keeping its `command`, `args`, and other settings:
297
103
 
298
- Use the exact project directory and destination task. If several Claude sessions match, specify the task ID. A `reply_received` receipt confirms a reply; a timeout does not mean the task stopped, so inspect it before retrying.
299
-
300
- Desktop sends and creation keep each call bounded to at most 40 seconds, including preflight and dispatch. Creation tools return a durable `deliveryId` once the fresh task is confirmed. `send_to_codex_thread` prepares its observation receipt before dispatch: a lost acknowledgement returns `deliveryStatus=unconfirmed`, not a claim of acceptance. On `nextAction=wait_codex_reply`, continue with that read-only tool until completion, a verification error, a request for user input, or cancellation. Reuse the same ID after reconnecting; do not resend the prompt. A receipt only permits observation and never grants access: original sender/accounts and current project permissions are checked again. An existing-thread receipt keeps the exact pre-send rollout boundary; a creation receipt requires the exact `create_thread` dispatch, executor, prompt and confirmed fresh thread ID. Unrelated turns cannot satisfy either receipt. Receipts live in `bridge-reply-receipts` under the configured Codex home and contain the prompt for correlation; keep them private. Creation deduplication receipts retain the original reply ID across retries, including edited briefs. Old creation receipts without a reply binding still prevent duplicates but require inspection; the bridge does not invent a binding retrospectively. This is continuation across bounded tool calls, not an unattended push service after Claude stops running.
301
-
302
- An explicit request for a new conversation, or standing user instructions such as the example above, authorizes creation for independent work. Keep fixes, clarifications and results for unfinished work in its original conversation. A completed assistant turn does not by itself mean the whole task is finished; matching a project or finding a recent task does not make it the right destination for new work.
303
-
304
- In Desktop mode, generate a fresh UUID `requestId` for each independent task and retain it on every retry of that creation. Different request IDs create separate tasks even with the same directory, title and prompt. The same ID recovers the original receipt after a bridge restart; an edited title or prompt is not resent. Continue that task with its returned `threadId`. Pending or uncertain creation remains blocked: inspect the original receipt/task instead of changing the ID to retry.
305
-
306
- For backward compatibility, calls without `requestId` retain the previous deduplication by directory and explicit title (or exact prompt when no title is supplied). They can return an older task with the same title. Use a fresh request ID for independent new work; keep the old call shape when recovering an earlier legacy creation. Legacy app-server mode rejects `requestId` before creating anything because durable creation deduplication is only supported through Desktop native delivery.
307
-
308
- The sending tools ask agents to write each prompt in English with these sections, dropping any that do not apply: `Goal`, `Context`, `Task`, `Scope`, `Constraints`, `Done when`, `Reply format`. The first line names the sender, the project and the purpose. Text the user supplied is sent unchanged.
309
-
310
- ```text
311
- [From Claude Code · my-app · edit coordination]
312
-
313
- ## Goal
314
- Avoid conflicting edits while Claude Code patches `src/export.ps1`.
315
-
316
- ## Task
317
- 1. Do not modify `src/export.ps1` or `README.txt` until told otherwise.
318
- 2. Write any unsaved edits to those files to disk now.
319
-
320
- ## Reply format
321
- Exactly one line: `DONE — changed: <files>` or `DONE — no changes`.
322
- ```
323
-
324
- ## Important behavior
325
-
326
- - Desktop mode uses the native relay and the apps' permissions; it does not fall back to an external app-server.
327
- - Account switches are checked before delivery. Missing identity or incompatible permissions block sending.
328
- - Access settings are preserved on reinstall. Review allowed workspaces before enabling the bridge.
329
- - CLI/app-server setup, all tools, and advanced settings are in the [reference](https://github.com/buidangminh23/codex-mcp-bridge/blob/main/REFERENCE.md).
330
-
331
- ## Update
332
-
333
- ```bash
334
- npm install -g @minhspark/codex-mcp-bridge@latest
104
+ ```toml
105
+ [mcp_servers.codex-native-relay]
106
+ env_vars = ["CODEX_APP_TOOLS_PIPE_PATH"]
335
107
  ```
336
108
 
337
- Supervisor-based installs reload compatible updates when idle. Older installs or changed MCP settings need a one-time reconnect; see [upgrade instructions](https://github.com/buidangminh23/codex-mcp-bridge/blob/main/REFERENCE.md#upgrading-an-install-you-already-have).
109
+ Append the variable name if `env_vars` already exists. Reconnect the relay in Codex Desktop after editing.
338
110
 
339
- On Windows, use `npm.cmd` if PowerShell blocks `npm.ps1`. Upgrade the Codex CLI with the same manager used to install it: `winget upgrade --id OpenAI.Codex --exact` for the Windows path above, or `npm install -g @openai/codex@latest` for the macOS/Linux path. Updating the bridge does not update the clients.
340
-
341
- If Node moved or a registration still points at an old installation, rerun the corresponding platform registration steps and reconnect its MCP server. Preserve existing access settings. Use `--no-bootstrap` only when refreshing a relay that already has an executor.
342
-
343
- ## Troubleshooting
344
-
345
- ### Download or installation failed
111
+ ### Linux / WSL (Bash)
346
112
 
347
- | Error / symptom | What to check and how to fix it |
348
- |---|---|
349
- | `node`, `npm`, or a bridge command is not found | Open a new terminal. Check `node --version` and `npm --version`. On Windows run `Get-Command node.exe` and `npm.cmd prefix -g`; the global prefix must be on PATH. On macOS/Linux run `command -v node` and `npm prefix -g`; its `bin` directory must be on PATH. Reload your Node version manager's shell setup if used. |
350
- | `EBADENGINE`, missing `WebSocket`, or Node older than 22 | Switch to Node 24 with the installer/version manager above, reinstall the bridge under that Node, then refresh MCP registrations that reference an old executable. |
351
- | PowerShell says `npm.ps1` or an installer script cannot be loaded | Use `npm.cmd` and the bridge installer's `.cmd` command shown above. For Codex use `codex.exe`; keep machine execution policies unchanged. |
352
- | `EACCES` on macOS/Linux | Use a user-owned Node version manager, then reinstall globally under that Node. See npm's [permission error guide](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/). |
353
- | `EPERM`, `EBUSY`, or a file is in use on Windows | Let active work finish, close the process named in the error if it owns the package files, and retry the same npm command. Check the reported path's permissions or security-software event if it persists. |
354
- | `E404` for the bridge | Check the exact package name `@minhspark/codex-mcp-bridge`. Run the registry checks below; a private mirror may not contain the package. |
355
- | `ETIMEDOUT`, `ECONNRESET`, DNS, proxy, or certificate errors | Run the registry checks below. Correct the configured proxy or use the CA certificate supplied by the network administrator. Keep TLS verification enabled. |
356
- | Claude installer returns HTML, `403`, or a curl error | Use the alternatives and error-specific fixes in [Claude Code installation troubleshooting](https://code.claude.com/docs/en/troubleshooting). |
113
+ Use Node.js 22+ and signed-in Codex and Claude Code CLIs under the same Linux user. This mode uses a local app-server; native Desktop integration requires Windows or macOS.
357
114
 
358
- Run these registry and cache diagnostics; in PowerShell replace `npm` with `npm.cmd`:
115
+ <details>
116
+ <summary>CLI registration commands</summary>
359
117
 
360
118
  ```bash
361
- npm config get registry
362
- npm ping
363
- npm view @minhspark/codex-mcp-bridge version
364
- npm view @minhspark/codex-mcp-bridge version --registry=https://registry.npmjs.org/
365
- npm cache verify
119
+ npm install -g @openai/codex@latest
120
+ codex login
121
+ CODEX_BRIDGE_DESKTOP_TASKS=0 claude-mcp-bridge-install
122
+ bridge_root="$(npm root -g)/@minhspark/codex-mcp-bridge"
123
+ claude mcp add --scope user codex-bridge \
124
+ -e CODEX_BIN="$(command -v codex)" \
125
+ -e CODEX_BRIDGE_DESKTOP_TASKS=0 \
126
+ -e CODEX_BRIDGE_AUTOSTART=1 \
127
+ -e CODEX_BRIDGE_THREAD_POLICY=roots \
128
+ -e CODEX_BRIDGE_ALLOWED_ROOTS="$HOME/YourProject" \
129
+ -- "$(command -v node)" "$bridge_root/src/mcp-supervisor.mjs" index.mjs
366
130
  ```
367
131
 
368
- If the public registry works but a configured mirror does not, update the mirror configuration or, where permitted, install once from the public registry:
132
+ Replace `$HOME/YourProject` with the actual project directory. WSL and Windows use separate paths and registrations.
369
133
 
370
- ```bash
371
- npm install -g @minhspark/codex-mcp-bridge@latest --registry=https://registry.npmjs.org/
372
- ```
134
+ </details>
373
135
 
374
- ### Installed, but the bridge does not connect
136
+ ### Verify the installation
375
137
 
376
- Start with `codex doctor` for Codex installation problems and `claude doctor` for Claude Code. Then inspect the MCP registrations and the status tools in [verification](#verify-the-installation).
138
+ Reconnect the affected MCP servers in the actual client tasks; Claude Code provides `/mcp`. Run these **MCP tools** inside the apps:
377
139
 
378
- | Error / symptom | Fix |
140
+ | Client | Tool |
379
141
  |---|---|
380
- | Codex says `Organization settings could not be loaded`, or the bridge reports `INVALID_MANAGED_CONFIG` | On a machine you administer, rerun `codex-mcp-bridge-install --full-access` to repair the managed policy and global defaults. The policy must include `"read-only"` alongside `"danger-full-access"`; the former is required for Codex to load it, while the effective mode remains Full access. Complete any OS administrator prompt, then run `codex doctor --summary --ascii`. |
381
- | `codex binary not found`, `ENOENT`, or Windows `EINVAL` during registration | Locate the actual executable. Set `CODEX_EXE` before rerunning the installer: PowerShell `$env:CODEX_EXE = (Get-Command codex.exe).Source`; macOS/Linux `export CODEX_EXE="$(command -v codex)"`. On Windows, do not point it at `codex.ps1` or `codex.cmd`. |
382
- | Tools appear in Claude Desktop but not in its Code task | Complete the separate [Claude Code registration](#register-claude-code), then reconnect `/mcp` in that Code session. |
383
- | `This MCP process has no registered Claude Desktop Code session in its parent ancestry` | The shared Desktop entry may not belong to the calling Code task. Register the bridge in the intended existing Code project under a distinct name, such as `codex-bridge-code`, retaining its access settings. Check that `CODEX_BRIDGE_ALLOWED_ROOTS` includes the intended authorized target project; copied test registrations may still allow only test directories. Reload that task's MCP configuration. In the tested Windows Desktop version, View > Reload was insufficient: fully exit and reopen Claude after active work is stopped, then reopen the same task. Verify the dedicated entry and a new proactive send; receiving a reply to a Codex-originated message alone does not establish proactive sending. |
384
- | Installer refuses an entry with custom access/timeout settings | Keep those settings. Update only the existing entry's `command` and `args` to the values printed by the installer, then reconnect. |
385
- | Desktop task still reports `app-server` | Rerun `codex-mcp-bridge-install --desktop-tasks`; set `CODEX_BRIDGE_DESKTOP_TASKS=1` in the separate Claude Code registration too. Refresh the reverse registration with the same setting and reconnect the actual sending task. |
386
- | Relay is installed but unavailable | Check [native relay pipe forwarding](#native-relay-pipe-forwarding), then reconnect `codex-native-relay` or restart Codex Desktop after active work finishes. Check `codex mcp get codex-native-relay` and the in-task `native_relay_status`; a registered entry alone is insufficient. |
387
- | `RELAY_THREAD_UNCONFIGURED` | Rerun `codex-native-relay-install --desktop-tasks` without `--no-bootstrap` to create the missing executor. |
388
- | macOS `untrusted-code-signing-identity` or `NATIVE_DELIVERY_UNCONFIRMED` | Inspect the client logs and the installer's `relay runtime:` line. Rerun the relay installer with Codex Desktop installed; if runtime detection fails, set `CODEX_NATIVE_RELAY_NODE` to the actual app-bundled runtime. Relaunch the companion after active work finishes. Inspect any original delivery before retrying. |
389
- | Linux says native relay unavailable | Use the Linux CLI setup with `CODEX_BRIDGE_DESKTOP_TASKS=0` on both registrations. Native Desktop relay support is Windows/macOS only. |
390
- | No Claude sessions or no matching saved project | Keep the intended Code session open under the same OS user. In Desktop mode, use a Claude Desktop Code session and an existing saved Codex project with the exact local path. Check both clients are signed in. |
391
- | `NOT AUTHORIZED` / workspace refused | Inspect `CODEX_BRIDGE_ALLOWED_ROOTS` and `CODEX_BRIDGE_THREAD_POLICY`. Add the intended writable project path to the relevant registration and reconnect; retain unrelated restrictions. |
392
- | Account identity unavailable / changed | Complete sign-in in the intended clients and recheck their status. Desktop routing requires supported local account identity; API-key or unsupported credential storage is not a substitute. See [account requirements](https://github.com/buidangminh23/codex-mcp-bridge/blob/main/REFERENCE.md#switching-desktop-accounts). |
393
- | Runtime is stale or update pending | Check the configured installation path and `autoReload` status. Active calls and unresolved deliveries defer a reload. Let them finish; reconnect once for legacy registrations or changed environment variables. |
394
- | Task is “open in another application” | For Desktop tasks, use the native mode above. For CLI/app-server tasks, let the owning turn finish and release its subscription before opening elsewhere. |
395
- | CLI mode cannot connect after reboot | Check `codex_bridge_status`, the configured endpoint, and whether autostart is enabled. The CLI setup uses `ws://127.0.0.1:8791`. If manually starting `codex app-server --listen ws://127.0.0.1:8791`, first confirm no server already owns that endpoint. |
396
- | CLI server says the model needs a newer Codex | Update Codex, then restart the specific old app-server after its active work finishes. Updating files does not replace an already running process. |
397
- | Send timed out / reply unconfirmed | Read the original task or delivery receipt before retrying. A timeout does not cancel the task, and retrying can send it twice. |
398
-
399
- For unresolved failures, [open an issue](https://github.com/buidangminh23/codex-mcp-bridge/issues) with the OS, Node/bridge/client versions, the failing command, and the relevant redacted status/error. Leave out tokens, credentials, and private conversations. More detail is in the [technical reference](https://github.com/buidangminh23/codex-mcp-bridge/blob/main/REFERENCE.md#troubleshooting).
400
-
401
- ## Repository analytics
402
-
403
- The live dashboard polls the aggregate API every 30 seconds. Installation counts reflect reports received by the server; this is not a count of currently online processes. Public GitHub/npm sources are refreshed with a short cache, but their own statistics may be delayed. GitHub Actions refreshes and archives statistics hourly through the `Repository analytics` workflow, including private GitHub traffic; scheduled runs may be delayed by GitHub. Data collection and publication to the `analytics` branch run independently of the Pages deployment environment, and a newer scheduled run replaces a stuck older run. That workflow reads GitHub traffic with the `ANALYTICS_TOKEN` repository secret, a token with push access to this repository (fine-grained: Administration read); the default workflow token cannot read GitHub traffic, so without it the views/clones sources are retained from the last successful run and the run is reported as incomplete. The `analytics` branch is written with the workflow's own token, so its commits come from `github-actions[bot]` and `ANALYTICS_TOKEN` needs no write access. The README view badge is an hourly snapshot and may be cached by GitHub; open the live dashboard for automatic updates. Each source keeps its own collection time and reporting window. Sources older than three hours are marked stale even when polling succeeds. Missing data is unavailable, not zero; an empty usage breakdown means no opted-in reports in that period. Only aggregate figures are published. Installation IDs stay in the private database.
404
-
405
- Repository owners can view [GitHub traffic](https://github.com/buidangminh23/codex-mcp-bridge/graphs/traffic) for recent views and clones. Downloads and clones include updates, reinstalls, and automation; they do not measure active users. GitHub traffic only covers the recent 14-day window, so collect it regularly to keep a longer history.
406
-
407
- From a source checkout with Node 22+ and GitHub CLI authenticated as an account with repository traffic access:
142
+ | Claude Code | `codex_bridge_status` |
143
+ | Codex | `claude_bridge_status` |
144
+ | Codex Desktop | `native_relay_status` |
408
145
 
409
- ```bash
410
- gh auth status
411
- npm run analytics
412
- ```
146
+ Send a short message and confirm its reply **in both directions**. Registration alone does not prove delivery.
413
147
 
414
- The collector saves `history.json` and a self-contained `index.html` dashboard privately:
415
-
416
- | Platform | Default directory |
417
- |---|---|
418
- | Windows | `%LOCALAPPDATA%\codex-mcp-bridge\analytics` |
419
- | macOS | `~/Library/Application Support/codex-mcp-bridge/analytics` |
420
- | Linux | `${XDG_DATA_HOME:-~/.local/share}/codex-mcp-bridge/analytics` |
148
+ ## Use
421
149
 
422
- Open `index.html` in a browser. Back up `history.json` to preserve the archive. Use `npm run analytics -- --output <directory>` to choose another private directory. Each run refreshes overlapping dates without double-counting, records per-source collection times, and preserves earlier successful data if a source fails. Daily unique visitors/cloners cannot be summed to estimate unique people across days. Release asset download counters are also retained in the JSON archive.
150
+ - **Claude → Codex:** “Send this review to my Codex task in this project and wait for its reply.”
151
+ - **Codex → Claude:** “Send this result to my Claude Desktop Code session in this project.”
152
+ - **New work:** “Create a new Codex conversation for this feature and send the brief there.”
423
153
 
424
- Run this command daily through a local scheduler or Codex automation while the machine is available. Scheduling is not installed by the package. A missed interval longer than GitHub's retention window cannot be recovered. For collection errors, check `gh auth status`, repository permissions, network connectivity, and API limits; rerun after correcting the cause. Never commit the private output directory.
154
+ Continue unfinished work with its original `threadId`. For an authorized new Desktop task, use `delegate_to_codex` or `start_codex_thread` with a fresh UUID `requestId`; retain that ID on retries. If a send times out, use `wait_codex_reply` with the original `deliveryId` before retrying the message.
425
155
 
426
- To refresh the public dashboard, run `npm run analytics:publish`. It publishes an allowlisted aggregate snapshot to the separate `analytics` data branch and leaves application source unchanged. To include installation metrics, run `scripts/usage-summary.sql` through the owner's Supabase SQL editor or connector, save the returned `summary` object privately as `usage-summary.json`, and pass `--usage <path-to-usage-summary.json>` to the publisher. The summary query returns counts only. Do not supply raw installation rows or service credentials.
156
+ [Daily handoffs](docs/daily-handoffs.md) · [Project permissions](docs/project-onboarding.md) · [All tools and settings](https://github.com/buidangminh23/codex-mcp-bridge/blob/main/REFERENCE.md)
427
157
 
428
- ### Optional active-install statistics
158
+ ## ChatGPT plugin
429
159
 
430
- Usage reporting is **off by default**. Each end user must explicitly enable it:
160
+ [Open Codex Bridge](https://codex-mcp-bridge.buidangminh23.chatgpt.site) to connect ChatGPT to a selected project on a Windows or macOS computer. After installing the package and native relay above, pair the connector:
431
161
 
432
162
  ```bash
433
- codex-mcp-bridge telemetry enable
434
- codex-mcp-bridge telemetry status
435
- codex-mcp-bridge telemetry disable
436
- ```
437
-
438
- The equivalent `claude-mcp-bridge telemetry ...` commands share the same local consent. When enabled, the bridge checks at startup and hourly while running, sending at most one successful report per UTC day to the project's Supabase endpoint: a random installation ID, UTC day, bridge version, and operating system. No chat content, paths, account identity, or credentials are included. Network infrastructure may process normal request metadata; the application's statistics table does not store IP addresses. Reporting failures do not interrupt bridge operation. `DO_NOT_TRACK=1` or `CODEX_BRIDGE_TELEMETRY=0` overrides local consent and suppresses reporting.
439
-
440
- These counts represent voluntarily reporting installations, not unique people or all users. Disabling stops future reports and removes the local installation ID. Previously submitted records older than 90 days are removed during subsequent ingestion. Source checkouts containing this feature support these commands; older published package versions do not.
441
-
442
- The owner can query `public.bridge_usage_daily` in the Supabase SQL editor. Public and authenticated client roles cannot read the table or call its ingestion function. The endpoint validates the project's public publishable key, so counts are approximate and can include fabricated IDs; its 10,000-record daily storage cap does not prevent request spam. Apply the migration under `supabase/migrations` before deploying `bridge-usage` with the supplied function configuration (custom publishable-key validation; legacy JWT verification disabled). The client contains only a public publishable key; service credentials stay in the Edge Function environment.
443
-
444
- ```sql
445
- SELECT day, count(*) AS reporting_installations
446
- FROM public.bridge_usage_daily
447
- GROUP BY day ORDER BY day DESC;
448
-
449
- SELECT count(DISTINCT install_id) AS reporting_installations_last_30_days
450
- FROM public.bridge_usage_daily
451
- WHERE day >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC')::date - 29;
163
+ codex-sites-connector --pair --site https://codex-mcp-bridge.buidangminh23.chatgpt.site --roots "/absolute/project/path"
452
164
  ```
453
165
 
454
- ## Development
455
-
456
- ### Glama directory
166
+ In PowerShell use `codex-sites-connector.cmd` and a Windows path such as `C:\Projects\YourProject`. Open the printed pairing URL, sign in, and select **Connect this computer**. Keep Codex Desktop and the connector running; use `codex-sites-connector` to reconnect later.
457
167
 
458
- The repository includes [glama.json](glama.json) with the maintainer's GitHub username, using [Glama's server schema](https://glama.ai/mcp/schemas/server.json). After changing it, use the listing's **Sync Server** action or repeat the ownership claim to refresh Glama's copy. See [Glama's manifest documentation](https://glama.ai/blog/2025-07-08-what-is-glamajson).
168
+ The MCP endpoint is `https://codex-mcp-bridge.buidangminh23.chatgpt.site/mcp`. Plugin availability follows [OpenAI's Sites access rules](https://help.openai.com/en/articles/20001547-hosting-a-plugin-with-chatgpt-sites); a public directory listing is not claimed.
459
169
 
460
- Glama's recent usage signal measures calls observed by Glama, independently of npm downloads and the bridge's opt-in installation analytics. Use **Try in Browser** and the read-only `codex_bridge_status` tool to check a hosted release. A cloud container cannot access the Codex Desktop app or signed-in sessions on a user's machine; run the bridge locally for those workflows. Related servers are managed separately in Glama's listing.
170
+ <details>
171
+ <summary>Hosted data and privacy</summary>
461
172
 
462
- If a Glama test reports `DockerBuildError` with HTTP `502 Bad Gateway` and an empty build log, the Docker service request failed without an application build log. Retry when that service recovers. If it persists, provide Glama support with the test ID, timestamp, and error details. Changing `npm ci` or the launcher cannot repair that upstream failure; investigate repository build steps only when their logs identify an application error.
173
+ The service is operated by Bui Dang Minh on OpenAI Sites. It stores a Site-scoped account ID, connector ID and token hash, timestamps, tool arguments (including prompts), and returned Desktop results. The operator can access these records. Local credentials and receipts stay in `~/.codex/sites-bridge`.
463
174
 
464
- ### Shared project scope for Desktop Code conversations
175
+ Dispatch expires after two minutes; operation history remains stored. Disconnecting revokes the connector and cancels queued work, but does not erase history or stop dispatched tasks. For deletion requests, contact the operator through the repository's support link without posting private content publicly. The hosted Worker implements no advertising or analytics collection.
465
176
 
466
- See [Desktop handoff design and recovery](docs/desktop-handoff-design.md) for the failure cases, integration with current upstream, and validation limits.
177
+ </details>
467
178
 
468
- For a source installation used by both desktops, set `CODEX_BRIDGE_PROJECT_POLICY` to the same absolute JSON file in both bridge entries. The opt-in policy is read on every operation. `bridge-projects` defaults to `~/.config/GptClaudeBridge/projects.json`; prefer a shared home-directory path over Windows AppData, whose view can differ between packaged applications.
469
-
470
- ```sh
471
- bridge-projects allow-parent /absolute/projects
472
- bridge-projects allow-project /absolute/other-repository
473
- bridge-projects check /absolute/other-repository
474
- bridge-projects revoke /absolute/other-repository
475
- ```
476
-
477
- A parent grant covers new projects inside it and their registered Git worktrees. A project grant covers that repository's registered worktrees even outside the parent. Canonical directory identities and Git's worktree registry are checked; a copied `.git` pointer does not establish membership. Explicit revocation takes precedence and applies to subsequent operations without restarting either bridge. It does not cancel work already dispatched. Missing or invalid policy files fail closed. This opt-in policy cannot replace the hardened profile's pinned roots.
478
-
479
- Project grants do not replace upstream's same-project requirement for Claude-to-Codex delivery: the verified sender and destination must still share a directory or registered Git repository. Authorizing two unrelated projects does not permit sending between them.
480
-
481
- Claude Code needs its own user-level `codex-bridge` process, whose ancestry identifies the sending Code session. A generic shared Desktop MCP process cannot supply that identity. `codex-bridge-code-install --policy /absolute/projects.json` previews migration of an existing supervised installation; `--apply --clients-stopped` backs up and writes it. Customized entries are refused for review. Synchronize the same entry in any external configuration manager such as CC Switch. `--check` diagnoses duplicate/shared registrations. Reconnect clients after changing registration; subsequent project-policy changes need no reconnect.
482
-
483
- Run `codex_bridge_status` in the actual Claude Code conversation with the intended `cwd`. It reports sender, target scope and relay readiness independently. A generic command-line connectivity check can report a reachable relay while returning nonzero because no Code caller was verified. Validate both a Codex-originated round trip and a Claude-originated round trip; neither proves the other. Account and sender permission checks remain mandatory. Client upgrades can still require compatibility updates.
179
+ ## Troubleshooting
484
180
 
485
- See the [project onboarding guide](docs/project-onboarding.md) for explicit setup and the optional settings adapter.
181
+ - **Command missing:** open a new terminal and check Node.js 22+ and the global npm directory on PATH.
182
+ - **PowerShell blocks scripts:** use `npm.cmd`, installer `.cmd` launchers, and `codex.exe`.
183
+ - **No sessions or wrong project:** open the intended Claude **Code** session and use the exact project directory saved in Codex Desktop.
184
+ - **Relay unavailable:** check [pipe forwarding](#native-relay-pipe-forwarding), reconnect, and run `native_relay_status` in the task.
185
+ - **Timeout or unconfirmed send:** inspect the original task or receipt before sending again.
486
186
 
487
- Automatic handoff requires both user authorization for the collaboration and permission to use the messaging tools; a card extension's project grant does not approve this bridge. After explicitly opting in, run `node scripts/configure-message-automation.mjs --apply --approve-message-automation` once. It backs up Claude user settings, adds only the exact `send_to_codex_thread` and `wait_codex_reply` tool permissions, and installs scoped collaboration guidance in `~/.claude/rules/gpt-claude-bridge.md`. Without flags it previews; `--check` verifies the files. Existing conflicting ask/deny rules are refused, not removed. It does not approve shell/file operations, expand projects, change modes, or override host safeguards. A running conversation can require a one-time user acknowledgment of standing authorization; do not disguise peer messages as user consent. Validate consecutive real handoffs after setup, not just connectivity. `readiness.readyScope` explicitly excludes host tool permission evaluation.
187
+ [More fixes](https://github.com/buidangminh23/codex-mcp-bridge/blob/main/REFERENCE.md#troubleshooting) · [Report an issue](https://github.com/buidangminh23/codex-mcp-bridge/issues)
488
188
 
489
- ```bash
490
- npm ci
491
- npm test
492
- ```
189
+ ## Optional active-install statistics
493
190
 
494
- On Windows, use `node --test --test-concurrency=2` (also avoids npm versions that reject forwarded flags). CI tests Node 22 and 24 on Linux, macOS, and Windows. Tests use isolated fixtures and do not spend model quota. For the optional telemetry endpoint, also run `deno test --allow-env supabase/functions/bridge-usage/contract-check.ts`.
191
+ Usage reporting is **off by default**. Manage it with `codex-mcp-bridge telemetry enable`, `telemetry status`, or `telemetry disable`. Reports contain a random installation ID, UTC day, version, and OS; no chat content, paths, account identity, or credentials. `DO_NOT_TRACK=1` or `CODEX_BRIDGE_TELEMETRY=0` disables reporting. Disabling removes the local ID; submitted records expire after 90 days during ingestion.
495
192
 
496
- [Contributing](CONTRIBUTING.md) · [Changelog](CHANGELOG.md) · [MIT license](LICENSE)
193
+ [Analytics dashboard](https://buidangminh23.github.io/codex-mcp-bridge/) · [Release notes](https://github.com/buidangminh23/codex-mcp-bridge/releases) · [Contributing](CONTRIBUTING.md) · [MIT license](LICENSE)
@@ -27,6 +27,24 @@ session identity, Git root if applicable, and Remote Control state. Compare
27
27
  Pin `expectedCwd` and `expectedTaskId` before dispatching production work.
28
28
  Reuse this verified parent for subsequent handoffs.
29
29
 
30
+ If a saved Claude project's directory has moved, discovery reports a separate
31
+ `relocatedProjects` candidate when the old directory is missing and the workspace
32
+ resolver finds one writable replacement authorized by the shared project policy.
33
+ Revoked original paths, revoked targets, ambiguous replacements and existing old
34
+ directories do not qualify. Hardened deployments do not remap projects.
35
+
36
+ The candidate can establish the project for an explicitly authorized new Code
37
+ conversation. It cannot resume the old task or authorize a live session: the
38
+ new native task, account, process, actual cwd and initial message must still match.
39
+ Discovery does not rewrite Claude metadata, create grants or clear revocations.
40
+ The saved source and its authorization are rechecked before opening, trusting and
41
+ submitting, including after a pending request is restored.
42
+
43
+ Native composer creation submits an initial user prompt; it is not a peer
44
+ message and does not establish a native sender label. If a handoff requires the
45
+ recipient's peer sender attribution, use `send_to_claude_session` with a verified
46
+ live task. Do not substitute composer submission or a text header for that route.
47
+
30
48
  If a link was already opened, retain its requestId and inspect its receipt.
31
49
  `promptSubmitted=false` with `submissionStatus=not_observed` means submission
32
50
  was not verified, not that nothing was delivered. A wrong-directory task can
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhspark/codex-mcp-bridge",
3
- "version": "1.22.3",
3
+ "version": "1.22.4",
4
4
  "description": "Two-way MCP bridge between Claude and Codex: prompts into a live Codex thread, messages into a running Claude Code session.",
5
5
  "keywords": [
6
6
  "mcp",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "codex-mcp-bridge",
4
- "version": "1.22.3",
4
+ "version": "1.22.4",
5
5
  "description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
6
6
  "author": {
7
7
  "name": "Bui Dang Minh",
@@ -23,7 +23,7 @@ import { enableCodexFullAccess, fullAccessEnabled } from "../src/codex-full-acce
23
23
 
24
24
  exitForVersionRequest(import.meta.url);
25
25
 
26
- const VERSION = "1.22.3";
26
+ const VERSION = "1.22.4";
27
27
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
28
28
  const entry = path.join(root, "src", "mcp-supervisor.mjs");
29
29
  const entryArgs = ["native-relay-companion.mjs"];
@@ -26,10 +26,11 @@ import { submitClaudeCodeComposer, trustClaudeCodeComposer } from "./claude-comp
26
26
  import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
27
27
  import { AGENT_PROMPT_GUIDANCE, PROMPT_FIELD_HINT } from "./prompt-guidance.mjs";
28
28
  import { registerProjectOnboardingTools } from "./project-onboarding-tools.mjs";
29
+ import { discoverClaudeProject } from "./claude-project-discovery.mjs";
29
30
 
30
31
  exitForVersionRequest(import.meta.url);
31
32
 
32
- const VERSION = "1.22.3";
33
+ const VERSION = "1.22.4";
33
34
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
34
35
  const FORWARD_MIN_INTERVAL_MS = 5000;
35
36
  const FORWARD_MAX_PER_SESSION = 50;
@@ -71,7 +72,8 @@ const creations = new ClaudeSessionCreation({
71
72
  submit: submitClaudeCodeComposer,
72
73
  trust: trustClaudeCodeComposer,
73
74
  settle: () => delay(2500),
74
- listTasks: (account) => readClaudeDesktopTasks({ account }).filter((task) => rootPolicy.allows(task.cwd)),
75
+ listTasks: (account) => readClaudeDesktopTasks({ account }),
76
+ resolveProject: (task) => discoverClaudeProject(task, { policy: rootPolicy })?.cwd ?? null,
75
77
  listSessions: () => listClaudeSessions().filter((session) => rootPolicy.allows(session.cwd)),
76
78
  readContext: (session, account) => readClaudeDesktopContext(session, { account }),
77
79
  readTranscript: (...args) => {
@@ -254,21 +256,25 @@ async function startClaudeCreation({ requestId, cwd, prompt, autoSubmit = proces
254
256
  sender: rootPolicy.capture(sender.cwd, "Codex sender working directory"),
255
257
  recipient: rootPolicy.capture(cwd, "Claude creation working directory"),
256
258
  } : null;
257
- const receipt = await creations.start({ requestId, cwd, prompt, autoSubmit, trustProject, account: accounts.claude, senderThreadId: sender.threadId,
258
- beforeOpen: async () => {
259
- runtime.assertCurrent();
260
- assertAccountIdentity(selectedAccounts);
261
- recheckScopeBindings(scopeBindings);
262
- if (rootPolicy.enabled && !rootPolicy.same(cwd, scopeBindings.recipient.path)) throw new Error("The Claude creation working directory changed before opening Desktop");
263
- const current = await assertSender(meta);
264
- if (JSON.stringify(current) !== JSON.stringify(sender)) throw new Error("The calling Codex turn changed before opening Claude Desktop");
265
- runtime.assertCurrent();
266
- assertAccountIdentity(selectedAccounts);
267
- recheckScopeBindings(scopeBindings);
268
- if (rootPolicy.enabled && !rootPolicy.same(cwd, scopeBindings.recipient.path)) throw new Error("The Claude creation working directory changed before opening Desktop");
269
- },
270
- });
271
- if (rootPolicy.enabled) creationRootBindings.set(requestId, scopeBindings);
259
+ let receipt;
260
+ try {
261
+ receipt = await creations.start({ requestId, cwd, prompt, autoSubmit, trustProject, account: accounts.claude, senderThreadId: sender.threadId,
262
+ beforeOpen: async () => {
263
+ runtime.assertCurrent();
264
+ assertAccountIdentity(selectedAccounts);
265
+ recheckScopeBindings(scopeBindings);
266
+ if (rootPolicy.enabled && !rootPolicy.same(cwd, scopeBindings.recipient.path)) throw new Error("The Claude creation working directory changed before opening Desktop");
267
+ const current = await assertSender(meta);
268
+ if (JSON.stringify(current) !== JSON.stringify(sender)) throw new Error("The calling Codex turn changed before opening Claude Desktop");
269
+ runtime.assertCurrent();
270
+ assertAccountIdentity(selectedAccounts);
271
+ recheckScopeBindings(scopeBindings);
272
+ if (rootPolicy.enabled && !rootPolicy.same(cwd, scopeBindings.recipient.path)) throw new Error("The Claude creation working directory changed before opening Desktop");
273
+ },
274
+ });
275
+ } finally {
276
+ if (rootPolicy.enabled && creations.requests.has(requestId) && !creationRootBindings.has(requestId)) creationRootBindings.set(requestId, scopeBindings);
277
+ }
272
278
  return { ...textResult(JSON.stringify(receipt, null, 2)), structuredContent: { creation: receipt } };
273
279
  }
274
280
 
@@ -414,13 +420,18 @@ registerTool(
414
420
  if (expectedCwd !== undefined) { try { assertClaudeSessionCwd(task, expectedCwd); } catch { return false; } }
415
421
  return true;
416
422
  }).map(({ taskId, title, cwd }) => ({ taskId, title, cwd, alive: sessions.some((session) => session.desktop?.taskId === taskId) }));
423
+ const relocatedProjects = saved.flatMap(task => {
424
+ const candidate = discoverClaudeProject(task, { policy: rootPolicy });
425
+ if (!candidate?.relocated || (expectedCwd !== undefined && !rootPolicy.same(candidate.cwd, expectedCwd))) return [];
426
+ return [{ taskId: task.taskId, originalCwd: candidate.originalCwd, resolvedCwd: candidate.cwd, liveVerified: false, resumable: false }];
427
+ });
417
428
  const summary = sessions.length
418
429
  ? `${sessions.length} Claude${desktopOnly ? " Desktop" : ""} session(s):\n\n${sessions.map(formatSessionRow).join("\n")}`
419
430
  : discovery.excluded.length
420
431
  ? `Found ${discovery.candidateCount} registered Claude Desktop candidate(s) with messaging endpoints, but their Desktop task identities could not be verified. Delivery remains blocked.`
421
432
  : desktopOnly ? missingDesktopSession : "No live Claude Code session found.";
422
- return { ...textResult([summary, ...discoveryDetails(discovery), ...(savedTasks.length ? [`Saved Desktop tasks (resume unfinished work with target auto and expectedTaskId):\n${JSON.stringify(savedTasks, null, 2)}`] : [])].join("\n")),
423
- structuredContent: { account: publicAccountState(account), sessions: sessions.map(({ socket, bridgeSessionId, ...session }) => session), savedTasks, discovery } };
433
+ return { ...textResult([summary, ...discoveryDetails(discovery), ...(savedTasks.length ? [`Saved Desktop tasks (resume unfinished work with target auto and expectedTaskId):\n${JSON.stringify(savedTasks, null, 2)}`] : []), ...(relocatedProjects.length ? [`Relocated project candidates (new conversation only when authorized; old tasks are not verified or resumable at the new path):\n${JSON.stringify(relocatedProjects, null, 2)}`] : [])].join("\n")),
434
+ structuredContent: { account: publicAccountState(account), sessions: sessions.map(({ socket, bridgeSessionId, ...session }) => session), savedTasks, relocatedProjects, discovery } };
424
435
  } catch (err) {
425
436
  return failure(err);
426
437
  }
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveWorkspacePath } from "./platform.mjs";
4
+
5
+ export function discoverClaudeProject(task, { policy, resolveWorkspace = resolveWorkspacePath }) {
6
+ if (!task || task.isArchived !== false || typeof task.taskId !== "string" || !task.taskId || task.fileTaskId !== task.taskId ||
7
+ typeof task.cwd !== "string" || !path.isAbsolute(task.cwd) || task.cwd.includes("\0")) return null;
8
+ try {
9
+ let cwd;
10
+ let relocated = false;
11
+ try {
12
+ cwd = fs.realpathSync.native(task.cwd);
13
+ } catch (error) {
14
+ if (error.code !== "ENOENT" || policy.mode !== "project-policy") return null;
15
+ try { fs.lstatSync(task.cwd); return null; } catch (missing) { if (missing.code !== "ENOENT") return null; }
16
+ policy.assertDiscoverySource(task.cwd);
17
+ const candidate = resolveWorkspace(task.cwd, { requireUnique: true });
18
+ if (!candidate.remapped || !candidate.writable) return null;
19
+ cwd = fs.realpathSync.native(candidate.path);
20
+ relocated = true;
21
+ }
22
+ const binding = policy.capture(cwd, "Discovered Claude project directory");
23
+ return { cwd: binding.path, originalCwd: task.cwd, relocated, binding };
24
+ } catch { return null; }
25
+ }
@@ -60,7 +60,7 @@ function receipt(request) {
60
60
  }
61
61
 
62
62
  export class ClaudeSessionCreation {
63
- constructor({ open, submit, trust, settle = async () => {}, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath = (directory) => {
63
+ constructor({ open, submit, trust, settle = async () => {}, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, resolveProject, realpath = (directory) => {
64
64
  const canonical = fs.realpathSync.native(directory);
65
65
  if (!fs.statSync(canonical).isDirectory()) throw new Error("Not a directory");
66
66
  return canonical;
@@ -68,7 +68,7 @@ export class ClaudeSessionCreation {
68
68
  for (const [name, dependency] of Object.entries({ open, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath })) {
69
69
  if (typeof dependency !== "function") throw new TypeError(`${name} must be a function`);
70
70
  }
71
- Object.assign(this, { open, submit, trust, settle, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath, platform });
71
+ Object.assign(this, { open, submit, trust, settle, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, resolveProject, realpath, platform });
72
72
  this.requests = new Map();
73
73
  this.queue = Promise.resolve();
74
74
  }
@@ -83,6 +83,7 @@ export class ClaudeSessionCreation {
83
83
  }
84
84
 
85
85
  sameCwd(left, right) {
86
+ if (typeof left !== "string" || typeof right !== "string") return false;
86
87
  return this.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
87
88
  }
88
89
 
@@ -123,11 +124,14 @@ export class ClaudeSessionCreation {
123
124
  if (sameAccount(existing.account, identity) && PENDING.has(existing.status) && !composerReleased) fail("CLAUDE_CREATION_PENDING", "A creation request is already pending for this account; inspect or explicitly abandon that request before opening another project in the shared composer.");
124
125
  }
125
126
  const tasks = await this.listTasks(account);
126
- if (!Array.isArray(tasks) || tasks.length > MAX_TASKS || tasks.some((task) => !string(task?.taskId, 256) || (task.fileTaskId !== undefined && task.fileTaskId !== task.taskId))) fail("CLAUDE_CREATION_TASKS_INVALID", "The existing native task baseline cannot be verified.");
127
- let existingProject = false;
127
+ const validTasks = value => Array.isArray(value) && value.length <= MAX_TASKS && value.every(task => string(task?.taskId, 256) && (task.fileTaskId === undefined || task.fileTaskId === task.taskId)) && new Set(value.map(task => task.taskId)).size === value.length;
128
+ if (!validTasks(tasks)) fail("CLAUDE_CREATION_TASKS_INVALID", "The existing native task baseline cannot be verified.");
129
+ const baseline = new Set(tasks.map(task => task.taskId));
130
+ const projectCwd = async task => this.resolveProject ? this.resolveProject(task) : this.canonical(task.cwd);
131
+ let existingProject;
128
132
  for (const task of tasks) {
129
133
  if (task.isArchived !== false) continue;
130
- try { if (this.sameCwd(await this.canonical(task.cwd), canonical)) existingProject = true; } catch {}
134
+ try { if (this.sameCwd(await projectCwd(task), canonical)) { existingProject = task; break; } } catch {}
131
135
  }
132
136
  if (!existingProject) fail("CLAUDE_CREATION_PROJECT_NOT_FOUND", "The canonical directory must already belong to a nonarchived Claude Desktop task.");
133
137
  if (autoSubmit && typeof this.submit !== "function") fail("CLAUDE_CREATION_SUBMIT_UNAVAILABLE", "Native composer submission is not available.");
@@ -139,7 +143,20 @@ export class ClaudeSessionCreation {
139
143
  if (typeof beforeOpen !== "function") fail("CLAUDE_CREATION_PREFLIGHT_INVALID", "beforeOpen must be a function.");
140
144
  await beforeOpen();
141
145
  }
142
- const request = { requestId, cwd: canonical, prompt, initialMessage, account: identity, senderThreadId, baseline: [...new Set(tasks.map((task) => task.taskId))], status: "launch_uncertain", reason: "The Desktop launch outcome is uncertain; inspect this request before taking further action." };
146
+ if (this.resolveProject) {
147
+ const currentTasks = await this.listTasks(account);
148
+ if (!validTasks(currentTasks)) fail("CLAUDE_CREATION_TASKS_INVALID", "The current native task baseline cannot be verified.");
149
+ const matches = currentTasks.filter(task => task.taskId === existingProject.taskId && task.fileTaskId === existingProject.fileTaskId && task.cwd === existingProject.cwd && task.isArchived === false);
150
+ if (matches.length !== 1 || !this.sameCwd(await projectCwd(matches[0]), canonical)) {
151
+ fail("CLAUDE_CREATION_PROJECT_NOT_FOUND", "The saved project or its authorized current directory changed before opening Desktop.");
152
+ }
153
+ for (const task of currentTasks) baseline.add(task.taskId);
154
+ if (baseline.size > MAX_TASKS) fail("CLAUDE_CREATION_TASKS_INVALID", "The native task baseline exceeds the creation limit.");
155
+ if (beforeOpen) await beforeOpen();
156
+ }
157
+ const projectSource = this.resolveProject ? { taskId: existingProject.taskId, fileTaskId: existingProject.fileTaskId, cwd: existingProject.cwd } : undefined;
158
+ const request = { requestId, cwd: canonical, prompt, initialMessage, account: identity, senderThreadId, baseline: [...baseline], ...(projectSource ? { projectSource } : {}), status: "launch_uncertain", reason: "The Desktop launch outcome is uncertain; inspect this request before taking further action." };
159
+ await this.revalidateProjectSource(request, account);
143
160
  this.requests.set(requestId, request);
144
161
  try {
145
162
  await this.open(url);
@@ -155,11 +172,25 @@ export class ClaudeSessionCreation {
155
172
  return receipt(request);
156
173
  }
157
174
 
175
+ async revalidateProjectSource(request, account) {
176
+ if (!this.resolveProject) return;
177
+ const source = request.projectSource;
178
+ if (!source) fail("CLAUDE_CREATION_PROJECT_NOT_FOUND", "The original saved project source is unavailable; submission is not authorized.");
179
+ const tasks = await this.listTasks(account);
180
+ if (!Array.isArray(tasks) || tasks.length > MAX_TASKS) fail("CLAUDE_CREATION_TASKS_INVALID", "The saved project source cannot be verified.");
181
+ const matches = tasks.filter(task => task?.taskId === source.taskId);
182
+ if (matches.length !== 1 || matches[0].fileTaskId !== source.fileTaskId || matches[0].cwd !== source.cwd || matches[0].isArchived !== false ||
183
+ !this.sameCwd(await this.resolveProject(matches[0]), request.cwd)) {
184
+ fail("CLAUDE_CREATION_PROJECT_NOT_FOUND", "The original saved project or its authorization changed; submission is not authorized.");
185
+ }
186
+ }
187
+
158
188
  async completeRequest(request, { account, senderThreadId, beforeOpen, trustProject }) {
159
189
  if (typeof this.submit !== "function") fail("CLAUDE_CREATION_SUBMIT_UNAVAILABLE", "Native composer submission is not available.");
160
190
  if (trustProject && typeof this.trust !== "function") fail("CLAUDE_CREATION_TRUST_UNAVAILABLE", "Authorized project trust preparation is not available.");
161
191
  await this.checkAccount(account, request.account);
162
192
  if (beforeOpen) await beforeOpen();
193
+ await this.revalidateProjectSource(request, account);
163
194
  if (trustProject) {
164
195
  const trusted = await this.trust({ cwd: request.cwd, prompt: request.initialMessage });
165
196
  if (!["trusted", "absent"].includes(trusted?.status)) {
@@ -171,6 +202,7 @@ export class ClaudeSessionCreation {
171
202
  }
172
203
  await this.checkAccount(account, request.account);
173
204
  if (beforeOpen) await beforeOpen();
205
+ await this.revalidateProjectSource(request, account);
174
206
  request.submissionAttempted = true;
175
207
  request.status = "submission_uncertain";
176
208
  request.reason = "Native submission was attempted; inspect this request before retrying. The bridge will not press Send again.";
@@ -178,6 +210,7 @@ export class ClaudeSessionCreation {
178
210
  const submission = await this.submit({ prompt: request.initialMessage, cwd: request.cwd, beforeSubmit: async () => {
179
211
  await this.checkAccount(account, request.account);
180
212
  if (beforeOpen) await beforeOpen();
213
+ await this.revalidateProjectSource(request, account);
181
214
  } });
182
215
  if (submission?.status === "blocked" && submission.submissionAttempted === false) delete request.submissionAttempted;
183
216
  request.status = submission?.status === "submitted" ? "awaiting_session" : submission?.status === "blocked" ? "awaiting_user" : "submission_uncertain";
@@ -263,7 +296,10 @@ export class ClaudeSessionCreation {
263
296
  if ((request.status === "awaiting_project_confirmation" || request.observedCwd !== undefined) && (!string(request.observedCwd, 8192) || !paths.isAbsolute(request.observedCwd))) fail("CLAUDE_CREATION_STATE_INVALID", "A retained observed directory is invalid.");
264
297
  if (request.status === "awaiting_project_confirmation" && (request.submissionObserved !== true || this.sameCwd(request.cwd, request.observedCwd))) fail("CLAUDE_CREATION_STATE_INVALID", "A pending project confirmation is invalid.");
265
298
  if (request.ambiguitySeen !== undefined && typeof request.ambiguitySeen !== "boolean") fail("CLAUDE_CREATION_STATE_INVALID", "A retained ambiguity marker is invalid.");
266
- restored.set(request.requestId, structuredClone({ requestId: request.requestId, cwd: request.cwd, prompt: request.prompt, initialMessage: request.initialMessage, account: { fingerprint: request.account.fingerprint, root: request.account.root }, senderThreadId: request.senderThreadId, baseline: request.baseline, status: request.status, reason: request.reason, ...(request.submissionAttempted ? { submissionAttempted: true } : {}), ...(request.ambiguitySeen || request.status === "ambiguous" ? { ambiguitySeen: true } : {}), ...(request.submissionObserved ? { submissionObserved: true } : {}), ...(request.observedCwd ? { observedCwd: request.observedCwd } : {}), ...(hasSession ? { sessionId: request.sessionId, taskId: request.taskId, title: request.title } : {}) }));
299
+ if (request.projectSource !== undefined && (!string(request.projectSource?.taskId, 256) || !request.baseline.includes(request.projectSource.taskId) ||
300
+ (request.projectSource.fileTaskId !== undefined && request.projectSource.fileTaskId !== request.projectSource.taskId) ||
301
+ !string(request.projectSource.cwd, 8192) || !paths.isAbsolute(request.projectSource.cwd))) fail("CLAUDE_CREATION_STATE_INVALID", "A retained project source is invalid.");
302
+ restored.set(request.requestId, structuredClone({ requestId: request.requestId, cwd: request.cwd, prompt: request.prompt, initialMessage: request.initialMessage, account: { fingerprint: request.account.fingerprint, root: request.account.root }, senderThreadId: request.senderThreadId, baseline: request.baseline, ...(request.projectSource ? { projectSource: { taskId: request.projectSource.taskId, fileTaskId: request.projectSource.fileTaskId, cwd: request.projectSource.cwd } } : {}), status: request.status, reason: request.reason, ...(request.submissionAttempted ? { submissionAttempted: true } : {}), ...(request.ambiguitySeen || request.status === "ambiguous" ? { ambiguitySeen: true } : {}), ...(request.submissionObserved ? { submissionObserved: true } : {}), ...(request.observedCwd ? { observedCwd: request.observedCwd } : {}), ...(hasSession ? { sessionId: request.sessionId, taskId: request.taskId, title: request.title } : {}) }));
267
303
  }
268
304
  this.requests = restored;
269
305
  }
package/src/index.mjs CHANGED
@@ -40,7 +40,7 @@ import { PROJECT_SETUP_TOOLS, registerProjectOnboardingTools } from "./project-o
40
40
 
41
41
  exitForVersionRequest(import.meta.url);
42
42
 
43
- const VERSION = "1.22.3";
43
+ const VERSION = "1.22.4";
44
44
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
45
45
  const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
46
46
 
@@ -38,7 +38,7 @@ import { NativeCreationReceipts, creationIdValid } from "./native-creation-recei
38
38
 
39
39
  exitForVersionRequest(import.meta.url);
40
40
 
41
- const VERSION = "1.22.3";
41
+ const VERSION = "1.22.4";
42
42
  const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
43
43
 
44
44
  function errorResponse(code, message, sent, details) {
package/src/platform.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile, execFileSync } from "node:child_process";
2
- import { accessSync, constants, existsSync, readdirSync, statSync } from "node:fs";
2
+ import { accessSync, constants, existsSync, readdirSync, realpathSync, statSync } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -338,7 +338,7 @@ function remapCandidates(input) {
338
338
  * checkout is unavailable locally. UNC shares are tried after local remap
339
339
  * candidates so a disconnected network location cannot stall the bridge.
340
340
  */
341
- export function resolveWorkspacePath(input) {
341
+ export function resolveWorkspacePath(input, { requireUnique = false } = {}) {
342
342
  if (!input) return { path: input, remapped: false, writable: false, note: null };
343
343
  const original = input;
344
344
  const mapped = pathMapCandidates(input);
@@ -348,7 +348,11 @@ export function resolveWorkspacePath(input) {
348
348
  : [...mapped, input, ...remapped];
349
349
  const ordered = candidates.filter((candidate, index) => candidates.indexOf(candidate) === index);
350
350
 
351
- const writable = ordered.find((candidate) => existsSync(candidate) && isWritableDir(candidate));
351
+ const usable = requireUnique ? ordered.filter((candidate) => existsSync(candidate) && isWritableDir(candidate)) : null;
352
+ if (usable && new Set(usable.map(candidate => realpathSync.native(candidate))).size > 1) {
353
+ throw new Error("Multiple usable workspace directories match; select an unambiguous project path");
354
+ }
355
+ const writable = usable ? usable[0] : ordered.find((candidate) => existsSync(candidate) && isWritableDir(candidate));
352
356
  if (writable) {
353
357
  return {
354
358
  path: path.resolve(writable),
@@ -150,6 +150,15 @@ export function createProjectScope(file, options = {}) {
150
150
  assert: (candidate, label) => capture(candidate, label).path,
151
151
  allows(candidate) { try { capture(candidate); return true; } catch { return false; } },
152
152
  capture,
153
+ assertDiscoverySource(candidate) {
154
+ const original = canonicalPolicyPath(candidate);
155
+ const namespaces = [path.resolve(candidate), original];
156
+ const { policy } = readProjectPolicy(file);
157
+ if (policy.denies.some(entry => namespaces.some(source => inside(entry.path, source) || entry.worktrees?.some(root => inside(root.path, source))))) {
158
+ throw new Error("The original project path was revoked; relocation discovery is not authorized");
159
+ }
160
+ return original;
161
+ },
153
162
  recheck(binding, label = "Working directory") {
154
163
  if (!binding?.identity) throw new Error(`${label} has no original directory identity`);
155
164
  const now = capture(binding.path, label);