authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-09 17:25:52-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 00:25:52+00:00
log140ee145ac33653ac2c36f7940e4f54f6e92497f
treeb21bdbdbdce5c5cfc0288263cb607c86fcdf3e35
parent5fe9d024d9b1c28ec93d495cf1441c57e47ae7ce
signature Signed by PGP key B5690EEEBB952194

fix(server): Codex shadow homes replace stray sqlite maintenance locks (#17663)

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,6 +140,34 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {
140 }),140 }),
141 );141 );
142142
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 it.effect.skipIf(!symlinksSupported)(171 it.effect.skipIf(!symlinksSupported)(
144 "replaces Codex-created local MCP OAuth locks with the shared lock directory",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,7 +30,9 @@ const KNOWN_SHARED_DIRECTORIES = [
3030
31const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]);31const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]);
32const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]);32const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]);
33const 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.
35const REPLACEABLE_SHARED_RUNTIME_ENTRIES = new Set(["mcp-oauth-locks", ".sqlite-maintenance.lock"]);
3436
35function resolveHomePath(path: Path.Path, home: string, value: string | undefined): string {37function resolveHomePath(path: Path.Path, home: string, value: string | undefined): string {
36 const expanded =38 const expanded =
...@@ -241,7 +243,7 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu...@@ -241,7 +243,7 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu
241 );243 );
242244
243 if (state._tag === "NotSymlink") {245 if (state._tag === "NotSymlink") {
244 if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) {246 if (!REPLACEABLE_SHARED_RUNTIME_ENTRIES.has(input.entryName)) {
245 return yield* new CodexShadowHomeEntryConflictError({247 return yield* new CodexShadowHomeEntryConflictError({
246 sharedHomePath: input.sharedHomePath,248 sharedHomePath: input.sharedHomePath,
247 effectiveHomePath: input.effectiveHomePath,249 effectiveHomePath: input.effectiveHomePath,