authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-17 23:26:53+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-17 23:26:53+02:00
log24f9c2a08e7b72a8f02b92aba229c34d35f72e30
tree0ef67f68c6f51e55a9a766f52fb4d8e22a756fb2
parent31ca9e553112ba336bfb3f6c04951f1086f37b0c
signature Signed by PGP key B5690EEEBB952194

Share MCP OAuth locks across Codex shadow homes (#4104)


2 files changed, 65 insertions(+), 10 deletions(-)

apps/server/src/provider/Drivers/CodexHomeLayout.test.ts+36
...@@ -114,6 +114,9 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {...@@ -114,6 +114,9 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {
114114
115 const sessionsTarget = yield* fileSystem.readLink(path.join(shadowHome, "sessions"));115 const sessionsTarget = yield* fileSystem.readLink(path.join(shadowHome, "sessions"));
116 const configTarget = yield* fileSystem.readLink(path.join(shadowHome, "config.toml"));116 const configTarget = yield* fileSystem.readLink(path.join(shadowHome, "config.toml"));
117 const mcpOauthLocksTarget = yield* fileSystem.readLink(
118 path.join(shadowHome, "mcp-oauth-locks"),
119 );
117 const modelsCacheExists = yield* fileSystem.exists(120 const modelsCacheExists = yield* fileSystem.exists(
118 path.join(shadowHome, "models_cache.json"),121 path.join(shadowHome, "models_cache.json"),
119 );122 );
...@@ -124,12 +127,45 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {...@@ -124,12 +127,45 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {
124127
125 expect(sessionsTarget).toBe(path.join(sharedHome, "sessions"));128 expect(sessionsTarget).toBe(path.join(sharedHome, "sessions"));
126 expect(configTarget).toBe(path.join(sharedHome, "config.toml"));129 expect(configTarget).toBe(path.join(sharedHome, "config.toml"));
130 expect(mcpOauthLocksTarget).toBe(path.join(sharedHome, "mcp-oauth-locks"));
127 expect(modelsCacheExists).toBe(false);131 expect(modelsCacheExists).toBe(false);
128 expect(authLinkResult._tag).toBe("Failure");132 expect(authLinkResult._tag).toBe("Failure");
129 expect(authContents).toContain("shadow");133 expect(authContents).toContain("shadow");
130 }),134 }),
131 );135 );
132136
137 it.effect("replaces Codex-created local MCP OAuth locks with the shared lock directory", () =>
138 Effect.gen(function* () {
139 const fileSystem = yield* FileSystem.FileSystem;
140 const path = yield* Path.Path;
141 const sharedHome = yield* makeTempDir("t3code-codex-shared-");
142 const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");
143 const shadowHome = path.join(shadowRoot, "shadow");
144 const sharedLocks = path.join(sharedHome, "mcp-oauth-locks");
145 const shadowLocks = path.join(shadowHome, "mcp-oauth-locks");
146
147 yield* writeTextFile(path.join(sharedLocks, "file-store.lock"), "");
148 yield* writeTextFile(path.join(shadowLocks, "file-store.lock"), "");
149
150 const layout = yield* resolveCodexHomeLayout(
151 decodeCodexSettings({
152 homePath: sharedHome,
153 shadowHomePath: shadowHome,
154 }),
155 );
156
157 yield* materializeCodexShadowHome(layout);
158
159 const locksTarget = yield* fileSystem.readLink(shadowLocks);
160 const sharedLockExists = yield* fileSystem.exists(
161 path.join(sharedLocks, "file-store.lock"),
162 );
163
164 expect(locksTarget).toBe(sharedLocks);
165 expect(sharedLockExists).toBe(true);
166 }),
167 );
168
133 it.effect("accepts Codex-created shadow-local runtime directories", () =>169 it.effect("accepts Codex-created shadow-local runtime directories", () =>
134 Effect.gen(function* () {170 Effect.gen(function* () {
135 const fileSystem = yield* FileSystem.FileSystem;171 const fileSystem = yield* FileSystem.FileSystem;
apps/server/src/provider/Drivers/CodexHomeLayout.ts+29-10
...@@ -26,10 +26,12 @@ const KNOWN_SHARED_DIRECTORIES = [...@@ -26,10 +26,12 @@ const KNOWN_SHARED_DIRECTORIES = [
26 "plugins",26 "plugins",
27 "cache",27 "cache",
28 "logs",28 "logs",
29 "mcp-oauth-locks",
29] as const;30] as const;
3031
31const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]);32const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]);
32const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]);33const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]);
34const REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = new Set(["mcp-oauth-locks"]);
3335
34function resolveHomePath(path: Path.Path, value: string | undefined): string {36function resolveHomePath(path: Path.Path, value: string | undefined): string {
35 const expanded =37 const expanded =
...@@ -225,16 +227,6 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu...@@ -225,16 +227,6 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu
225 linkPath: link,227 linkPath: link,
226 });228 });
227229
228 if (state._tag === "NotSymlink") {
229 return yield* new CodexShadowHomeEntryConflictError({
230 sharedHomePath: input.sharedHomePath,
231 effectiveHomePath: input.effectiveHomePath,
232 entryName: input.entryName,
233 linkPath: link,
234 targetPath: target,
235 });
236 }
237
238 const createLink = input.fileSystem.symlink(target, link).pipe(230 const createLink = input.fileSystem.symlink(target, link).pipe(
239 Effect.catchTags({231 Effect.catchTags({
240 PlatformError: (cause) =>232 PlatformError: (cause) =>
...@@ -250,6 +242,33 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu...@@ -250,6 +242,33 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu
250 }),242 }),
251 );243 );
252244
245 if (state._tag === "NotSymlink") {
246 if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) {
247 return yield* new CodexShadowHomeEntryConflictError({
248 sharedHomePath: input.sharedHomePath,
249 effectiveHomePath: input.effectiveHomePath,
250 entryName: input.entryName,
251 linkPath: link,
252 targetPath: target,
253 });
254 }
255
256 yield* input.fileSystem.remove(link, { recursive: true }).pipe(
257 Effect.catchTags({
258 PlatformError: (cause) =>
259 new CodexShadowHomeFileSystemError({
260 sharedHomePath: input.sharedHomePath,
261 effectiveHomePath: input.effectiveHomePath,
262 operation: "remove",
263 path: link,
264 entryName: input.entryName,
265 cause,
266 }),
267 }),
268 );
269 return yield* createLink;
270 }
271
253 if (state._tag === "Missing") {272 if (state._tag === "Missing") {
254 return yield* createLink;273 return yield* createLink;
255 }274 }