Repository navigation
fix(server): new Codex runtime entries no longer disable shadow-home providers #23472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b913ce5
51cf491
c3fd754
282a92e
e3e2907
73bcab7
8f4aafe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,19 @@ const REPLACEABLE_SHARED_RUNTIME_ENTRIES = new Set([ | |
| "thread-writer-locks", | ||
| ".sqlite-maintenance.lock", | ||
| ]); | ||
| // A local copy of these defeats the shared home, so it is reported instead of kept. Any other | ||
| // regular shadow entry is state Codex created for this account (new releases keep adding | ||
| // databases such as queue_1.sqlite), and it stays local rather than disabling the provider. | ||
| const REQUIRED_SHARED_ENTRY_NAMES = new Set<string>([...KNOWN_SHARED_DIRECTORIES, "config.toml"]); | ||
| const SQLITE_SIDECAR_SUFFIXES = ["-wal", "-shm", "-journal"] as const; | ||
|
|
||
| // Groups a SQLite database with its sidecars, which must all live in the same home. | ||
| function sqliteFamilyName(entryName: string): string { | ||
| const suffix = SQLITE_SIDECAR_SUFFIXES.find( | ||
| (candidate) => entryName.length > candidate.length && entryName.endsWith(candidate), | ||
| ); | ||
| return suffix === undefined ? entryName : entryName.slice(0, -suffix.length); | ||
| } | ||
|
|
||
| function resolveHomePath(path: Path.Path, home: string, value: string | undefined): string { | ||
| const expanded = | ||
|
|
@@ -388,25 +401,56 @@ export const materializeCodexShadowHome = Effect.fn("materializeCodexShadowHome" | |
| { concurrency: "unbounded" }, | ||
| ); | ||
|
|
||
| const sharedEntryNames = yield* fileSystem.readDirectory(layout.sharedHomePath).pipe( | ||
| Effect.catchTags({ | ||
| PlatformError: (cause) => | ||
| new CodexShadowHomeFileSystemError({ | ||
| sharedHomePath: layout.sharedHomePath, | ||
| effectiveHomePath, | ||
| operation: "readDirectory", | ||
| path: layout.sharedHomePath, | ||
| cause, | ||
| }), | ||
| }), | ||
| ); | ||
| const readDirectory = (directoryPath: string) => | ||
| fileSystem.readDirectory(directoryPath).pipe( | ||
| Effect.catchTags({ | ||
| PlatformError: (cause) => | ||
| new CodexShadowHomeFileSystemError({ | ||
| sharedHomePath: layout.sharedHomePath, | ||
| effectiveHomePath, | ||
| operation: "readDirectory", | ||
| path: directoryPath, | ||
| cause, | ||
| }), | ||
| }), | ||
| ); | ||
| const [sharedEntryNames, shadowEntryNames] = yield* Effect.all([ | ||
| readDirectory(layout.sharedHomePath), | ||
| readDirectory(effectiveHomePath), | ||
| ]); | ||
| const isSharable = (entryName: string) => | ||
| !PRIVATE_ENTRY_NAMES.has(entryName) && !SHADOW_LOCAL_ENTRY_NAMES.has(entryName); | ||
| const entries = new Set<string>(KNOWN_SHARED_DIRECTORIES); | ||
| for (const entryName of sharedEntryNames) { | ||
| if (!PRIVATE_ENTRY_NAMES.has(entryName) && !SHADOW_LOCAL_ENTRY_NAMES.has(entryName)) { | ||
| if (isSharable(entryName)) { | ||
| entries.add(entryName); | ||
| } | ||
| } | ||
|
|
||
| // A family with any regular shadow member stays exactly as it is: linking the rest would | ||
| // pair a local database with shared sidecars, or the reverse, and only SQLite can merge them. | ||
| const shadowLocalFamilies = new Set<string>(); | ||
| yield* Effect.forEach( | ||
| shadowEntryNames, | ||
| (entryName) => | ||
| isSharable(entryName) | ||
| ? readLinkState({ | ||
| fileSystem, | ||
| sharedHomePath: layout.sharedHomePath, | ||
| effectiveHomePath, | ||
| entryName, | ||
| linkPath: path.join(effectiveHomePath, entryName), | ||
| }).pipe( | ||
| Effect.map((state) => { | ||
| if (state._tag === "NotSymlink") { | ||
| shadowLocalFamilies.add(sqliteFamilyName(entryName)); | ||
| } | ||
| }), | ||
| ) | ||
| : Effect.void, | ||
| { discard: true }, | ||
| ); | ||
|
|
||
| yield* Effect.forEach( | ||
| PRIVATE_ENTRY_NAMES, | ||
| (entryName) => | ||
|
|
@@ -427,6 +471,19 @@ export const materializeCodexShadowHome = Effect.fn("materializeCodexShadowHome" | |
| if (PRIVATE_ENTRY_NAMES.has(entryName)) { | ||
| return Effect.void; | ||
| } | ||
| const familyName = sqliteFamilyName(entryName); | ||
| // Sidecars follow their base, so they are not linked next to a database the shadow home | ||
| // may create later. | ||
| if (familyName !== entryName && !entries.has(familyName)) { | ||
| return Effect.void; | ||
| } | ||
| if ( | ||
| shadowLocalFamilies.has(familyName) && | ||
| !REQUIRED_SHARED_ENTRY_NAMES.has(entryName) && | ||
| !REPLACEABLE_SHARED_RUNTIME_ENTRIES.has(entryName) | ||
| ) { | ||
| return Effect.void; | ||
|
Comment on lines
+480
to
+485
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not launch with a linked database and a stale local WAL. On Windows, a regular shadow
|
||
| } | ||
| return ensureSymlink({ | ||
| fileSystem, | ||
| sharedHomePath: layout.sharedHomePath, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove an existing sidecar link when skipping an orphan sidecar.
An earlier materialization can leave a shadow
-walsymlink. If the shared base is later removed and a regular shadow base exists, this branch skips the orphan sidecar but leaves that symlink in place. The local database can then use a WAL in the shared home, contrary to the intended local-family isolation. Remove the sidecar symlink without removing its shared target, or reject the mixed layout before Codex starts. (sqlite.org)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed: with a regular shadow base and an older shadow -wal link into the shared home, both skips (orphan sidecar at :478, shadow-local family at :481) leave the link, so the local DB journals into the shared file on Linux. main left it too (ensureSymlink keeps a correct link). Leaving open with the Critical thread, since the mixed-family policy decides how this should be cleaned up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@juliusmarminge Agreed. Both skip branches preserve the existing sidecar symlink. Your reproduction also establishes that this mixed layout predates the PR; it is not a new regression from these branches.
This finding should remain open alongside the Critical thread until the mixed-family policy is settled. My earlier unlink recommendation was too narrow: removing the link alone does not establish safe handling of existing WAL data. Verification should cover both skip paths with a regular shadow database and an existing sidecar symlink, and confirm that the chosen policy preserves committed data.
You are interacting with an AI system.