@minhspark/codex-mcp-bridge 1.22.8 → 1.22.9

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.8",
3
+ "version": "1.22.9",
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,23 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](ht
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.22.9] - 2026-10-20
8
+
9
+ ### Fixed
10
+
11
+ - Read the Claude Desktop configuration with exact bigint stats in `codex-mcp-bridge-install`, so a configuration file replaced during the installer's read is detected even when the two inodes differ only beyond Number precision. v1.22.8 converted the runtime stores but missed this installer.
12
+ - Notice a new reply in a Claude Code transcript when the file changes within the same millisecond and keeps its size. The reply poll now compares the file's device, inode, nanosecond modification and change times, and size instead of only the millisecond modification time and size.
13
+
14
+ ### Verification
15
+
16
+ - 2 new regression tests: a Desktop configuration whose inode changes only below Number precision (`2^60` and `2^60 + 1`) is rejected and left untouched, and a transcript reply is observed while its Number `mtimeMs` and size stay frozen. Both fail on v1.22.8.
17
+ - Two independent sweeps of `src/`, `scripts/` and `bin/`, one by stat API and one by stored or compared stat data, found no other Number-based identity or change detection.
18
+ - CI passed on macOS, Ubuntu and Windows with Node 22 and 24. CodeQL re-reported 3 `js/path-injection` flows on the edited stat lines; each source is operator environment configuration, matching earlier dismissals, and they were dismissed as false positives.
19
+
20
+ ### Limitations
21
+
22
+ - The 64-bit NTFS case runs for real only on Windows CI; local tests simulate it with mocked stats.
23
+
7
24
  ## [1.22.8] - 2026-10-20
8
25
 
9
26
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhspark/codex-mcp-bridge",
3
- "version": "1.22.8",
3
+ "version": "1.22.9",
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.8",
4
+ "version": "1.22.9",
5
5
  "description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
6
6
  "author": {
7
7
  "name": "Bui Dang Minh",
@@ -45,23 +45,23 @@ fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
45
45
  function readConfig() {
46
46
  let descriptor;
47
47
  try {
48
- const before = fs.lstatSync(cfgPath);
49
- if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > 4 * 1024 * 1024) throw new Error("Unsafe Desktop configuration file");
48
+ const before = fs.lstatSync(cfgPath, { bigint: true });
49
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > 4n * 1024n * 1024n) throw new Error("Unsafe Desktop configuration file");
50
50
  descriptor = fs.openSync(cfgPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
51
- const opened = fs.fstatSync(descriptor);
52
- const fields = ["dev", "ino", "size", "mtimeMs", "ctimeMs"];
51
+ const opened = fs.fstatSync(descriptor, { bigint: true });
52
+ const fields = ["dev", "ino", "size", "mtimeNs", "ctimeNs"];
53
53
  const matches = (left, right) => fields.every(field => left[field] === right[field]);
54
- if (!opened.isFile() || opened.nlink !== 1 || !matches(before, opened)) throw new Error("Desktop configuration changed during access");
55
- const data = Buffer.alloc(opened.size);
54
+ if (!opened.isFile() || opened.nlink !== 1n || !matches(before, opened)) throw new Error("Desktop configuration changed during access");
55
+ const data = Buffer.alloc(Number(opened.size));
56
56
  let offset = 0;
57
57
  while (offset < data.length) {
58
58
  const count = fs.readSync(descriptor, data, offset, data.length - offset, offset);
59
59
  if (!count) throw new Error("Desktop configuration changed during access");
60
60
  offset += count;
61
61
  }
62
- const after = fs.fstatSync(descriptor);
63
- const current = fs.lstatSync(cfgPath);
64
- if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1 || !matches(opened, after) || !matches(after, current)) throw new Error("Desktop configuration changed during access");
62
+ const after = fs.fstatSync(descriptor, { bigint: true });
63
+ const current = fs.lstatSync(cfgPath, { bigint: true });
64
+ if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1n || !matches(opened, after) || !matches(after, current)) throw new Error("Desktop configuration changed during access");
65
65
  return { data, version: fields.map(field => current[field]) };
66
66
  } catch (error) {
67
67
  if (error.code === "ENOENT" && descriptor === undefined) return null;
@@ -24,7 +24,7 @@ import { stableInstallPath } from "../src/install-paths.mjs";
24
24
 
25
25
  exitForVersionRequest(import.meta.url);
26
26
 
27
- const VERSION = "1.22.8";
27
+ const VERSION = "1.22.9";
28
28
  const root = stableInstallPath(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
29
29
  const entry = path.join(root, "src", "mcp-supervisor.mjs");
30
30
  const entryArgs = ["native-relay-companion.mjs"];
@@ -30,7 +30,7 @@ import { discoverClaudeProject } from "./claude-project-discovery.mjs";
30
30
 
31
31
  exitForVersionRequest(import.meta.url);
32
32
 
33
- const VERSION = "1.22.8";
33
+ const VERSION = "1.22.9";
34
34
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
35
35
  const FORWARD_MIN_INTERVAL_MS = 5000;
36
36
  const FORWARD_MAX_PER_SESSION = 50;
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.8";
43
+ const VERSION = "1.22.9";
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.8";
41
+ const VERSION = "1.22.9";
42
42
  const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
43
43
 
44
44
  function errorResponse(code, message, sent, details) {
@@ -860,7 +860,7 @@ export class PeerEndpoint {
860
860
  const { sessionId, cwd } = pending.transcriptSession;
861
861
  const file = findTranscriptFile(sessionId, cwd);
862
862
  let stamp;
863
- try { const stat = fs.statSync(file); stamp = `${stat.mtimeMs}:${stat.size}`; }
863
+ try { const stat = fs.statSync(file, { bigint: true }); stamp = `${stat.dev}:${stat.ino}:${stat.mtimeNs}:${stat.ctimeNs}:${stat.size}`; }
864
864
  catch { continue; }
865
865
  if (stamp === pending.transcriptStamp) continue;
866
866
  pending.transcriptStamp = stamp;