| author | |
| committer | |
| log | 140ee145ac33653ac2c36f7940e4f54f6e92497f |
| tree | b21bdbdbdce5c5cfc0288263cb607c86fcdf3e35 |
| parent | 5fe9d024d9b1c28ec93d495cf1441c57e47ae7ce |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>2 files changed, 32 insertions(+), 2 deletions(-)
apps/server/src/provider/Drivers/CodexHomeLayout.test.ts+28| ... | ... | @@ -140,6 +140,34 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => { |
| 140 | 140 | }), |
| 141 | 141 | ); |
| 142 | 142 | |
| 143 | it.effect.skipIf(!symlinksSupported)( | |
| 144 | "replaces a Codex-created local sqlite maintenance lock with the shared lock", | |
| 145 | () => | |
| 146 | Effect.gen(function* () { | |
| 147 | const fileSystem = yield* FileSystem.FileSystem; | |
| 148 | const path = yield* Path.Path; | |
| 149 | const sharedHome = yield* makeTempDir("t3code-codex-shared-"); | |
| 150 | const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-"); | |
| 151 | const shadowHome = path.join(shadowRoot, "shadow"); | |
| 152 | const sharedLock = path.join(sharedHome, ".sqlite-maintenance.lock"); | |
| 153 | const shadowLock = path.join(shadowHome, ".sqlite-maintenance.lock"); | |
| 154 | ||
| 155 | yield* writeTextFile(sharedLock, ""); | |
| 156 | yield* writeTextFile(shadowLock, ""); | |
| 157 | ||
| 158 | const layout = yield* resolveCodexHomeLayout( | |
| 159 | decodeCodexSettings({ | |
| 160 | homePath: sharedHome, | |
| 161 | shadowHomePath: shadowHome, | |
| 162 | }), | |
| 163 | ); | |
| 164 | ||
| 165 | yield* materializeCodexShadowHome(layout); | |
| 166 | ||
| 167 | expect(yield* fileSystem.readLink(shadowLock)).toBe(sharedLock); | |
| 168 | }), | |
| 169 | ); | |
| 170 | ||
| 143 | 171 | it.effect.skipIf(!symlinksSupported)( |
| 144 | 172 | "replaces Codex-created local MCP OAuth locks with the shared lock directory", |
| 145 | 173 | () => |
apps/server/src/provider/Drivers/CodexHomeLayout.ts+4-2| ... | ... | @@ -30,7 +30,9 @@ const KNOWN_SHARED_DIRECTORIES = [ |
| 30 | 30 | |
| 31 | 31 | const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]); |
| 32 | 32 | const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]); |
| 33 | const REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = new Set(["mcp-oauth-locks"]); | |
| 33 | // Codex creates these runtime locks itself when it starts in a shadow home before the shared | |
| 34 | // home has one. They guard shared state, so a local copy is replaced with the shared link. | |
| 35 | const REPLACEABLE_SHARED_RUNTIME_ENTRIES = new Set(["mcp-oauth-locks", ".sqlite-maintenance.lock"]); | |
| 34 | 36 | |
| 35 | 37 | function resolveHomePath(path: Path.Path, home: string, value: string | undefined): string { |
| 36 | 38 | const expanded = |
| ... | ... | @@ -241,7 +243,7 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu |
| 241 | 243 | ); |
| 242 | 244 | |
| 243 | 245 | if (state._tag === "NotSymlink") { |
| 244 | if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) { | |
| 246 | if (!REPLACEABLE_SHARED_RUNTIME_ENTRIES.has(input.entryName)) { | |
| 245 | 247 | return yield* new CodexShadowHomeEntryConflictError({ |
| 246 | 248 | sharedHomePath: input.sharedHomePath, |
| 247 | 249 | effectiveHomePath: input.effectiveHomePath, |