@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.
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +18 -0
- package/README.md +77 -380
- package/docs/daily-handoffs.md +18 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/install-native-relay.mjs +1 -1
- package/src/claude-bridge.mjs +30 -19
- package/src/claude-project-discovery.mjs +25 -0
- package/src/claude-session-creation.mjs +43 -7
- package/src/index.mjs +1 -1
- package/src/native-relay-companion.mjs +1 -1
- package/src/platform.mjs +7 -3
- package/src/project-policy.mjs +9 -0
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&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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=$
|
|
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
|
|
239
|
-
--
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
+
### Native relay pipe forwarding
|
|
293
101
|
|
|
294
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
109
|
+
Append the variable name if `env_vars` already exists. Reconnect the relay in Codex Desktop after editing.
|
|
338
110
|
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
<details>
|
|
116
|
+
<summary>CLI registration commands</summary>
|
|
359
117
|
|
|
360
118
|
```bash
|
|
361
|
-
npm
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
npm
|
|
365
|
-
|
|
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
|
-
|
|
132
|
+
Replace `$HOME/YourProject` with the actual project directory. WSL and Windows use separate paths and registrations.
|
|
369
133
|
|
|
370
|
-
|
|
371
|
-
npm install -g @minhspark/codex-mcp-bridge@latest --registry=https://registry.npmjs.org/
|
|
372
|
-
```
|
|
134
|
+
</details>
|
|
373
135
|
|
|
374
|
-
###
|
|
136
|
+
### Verify the installation
|
|
375
137
|
|
|
376
|
-
|
|
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
|
-
|
|
|
140
|
+
| Client | Tool |
|
|
379
141
|
|---|---|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
+
## ChatGPT plugin
|
|
429
159
|
|
|
430
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
170
|
+
<details>
|
|
171
|
+
<summary>Hosted data and privacy</summary>
|
|
461
172
|
|
|
462
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
+
</details>
|
|
467
178
|
|
|
468
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
490
|
-
npm ci
|
|
491
|
-
npm test
|
|
492
|
-
```
|
|
189
|
+
## Optional active-install statistics
|
|
493
190
|
|
|
494
|
-
|
|
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
|
-
[
|
|
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)
|
package/docs/daily-handoffs.md
CHANGED
|
@@ -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
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.
|
|
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.
|
|
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"];
|
package/src/claude-bridge.mjs
CHANGED
|
@@ -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.
|
|
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 })
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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),
|
package/src/project-policy.mjs
CHANGED
|
@@ -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);
|