Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions apps/server/src/provider/Drivers/CodexHomeLayout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,57 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {
}),
);

it.effect("keeps a SQLite family local when Codex creates it between passes", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const sharedHome = yield* makeTempDir("t3code-codex-shared-");
const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");
const shadowHome = path.join(shadowRoot, "shadow");
const layout = yield* resolveCodexHomeLayout(
decodeCodexSettings({
homePath: sharedHome,
shadowHomePath: shadowHome,
}),
);

yield* materializeCodexShadowHome(layout);

for (const suffix of ["", "-wal", "-shm"]) {
yield* writeTextFile(path.join(sharedHome, `queue_1.sqlite${suffix}`), `shared${suffix}`);
yield* writeTextFile(path.join(shadowHome, `queue_1.sqlite${suffix}`), `shadow${suffix}`);
yield* writeTextFile(
path.join(sharedHome, `future_runtime.sqlite${suffix}`),
`future-shared${suffix}`,
);
}
yield* writeTextFile(path.join(shadowHome, "future_runtime.sqlite"), "future-shadow");

yield* materializeCodexShadowHome(layout);
yield* materializeCodexShadowHome(layout);

for (const suffix of ["", "-wal", "-shm"]) {
const entryName = `queue_1.sqlite${suffix}`;
const sharedPath = path.join(sharedHome, entryName);
const shadowPath = path.join(shadowHome, entryName);

expect(yield* fileSystem.readFileString(sharedPath)).toBe(`shared${suffix}`);
expect(yield* fileSystem.readFileString(shadowPath)).toBe(`shadow${suffix}`);
expect((yield* fileSystem.readLink(shadowPath).pipe(Effect.result))._tag).toBe("Failure");
}

expect(
yield* fileSystem.readFileString(path.join(shadowHome, "future_runtime.sqlite")),
).toBe("future-shadow");
expect(yield* fileSystem.exists(path.join(shadowHome, "future_runtime.sqlite-wal"))).toBe(
false,
);
expect(yield* fileSystem.exists(path.join(shadowHome, "future_runtime.sqlite-shm"))).toBe(
false,
);
}),
);

it.effect("rejects shadow homes that point at the shared home", () =>
Effect.gen(function* () {
const sharedHome = yield* makeTempDir("t3code-codex-shared-");
Expand All @@ -235,6 +286,7 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {

it.effect("rejects shared entries that already exist in the shadow home as real files", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const sharedHome = yield* makeTempDir("t3code-codex-shared-");
const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");
Expand Down Expand Up @@ -262,6 +314,19 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {
expect(error.message).toBe(
`Cannot create Codex shadow home entry 'config.toml' because '${path.join(shadowHome, "config.toml")}' already exists and is not a symlink.`,
);
expect(yield* fileSystem.readFileString(path.join(sharedHome, "config.toml"))).toBe(
'model = "gpt-5-codex"\n',
);
expect(yield* fileSystem.readFileString(path.join(shadowHome, "config.toml"))).toBe(
'model = "local"\n',
);

yield* fileSystem.remove(path.join(shadowHome, "config.toml"));
yield* materializeCodexShadowHome(layout);

expect(yield* fileSystem.readLink(path.join(shadowHome, "config.toml"))).toBe(
path.join(sharedHome, "config.toml"),
);
}),
);

Expand Down
54 changes: 48 additions & 6 deletions apps/server/src/provider/Drivers/CodexHomeLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ const KNOWN_SHARED_DIRECTORIES = [
const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]);
const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]);
const REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = new Set(["mcp-oauth-locks"]);
const SQLITE_FAMILY_SUFFIXES = ["", "-wal", "-shm"] as const;

function sqliteFamilyBaseName(entryName: string): string | undefined {
const baseName = entryName.replace(/-(?:wal|shm)$/, "");
return baseName.endsWith(".sqlite") ? baseName : undefined;
}

function resolveHomePath(path: Path.Path, value: string | undefined): string {
const expanded =
Expand Down Expand Up @@ -184,27 +190,27 @@ const readLinkState = Effect.fn("CodexHomeLayout.readLinkState")(function* (inpu
);
});

const removePrivateSymlink = Effect.fn("CodexHomeLayout.removePrivateSymlink")(function* (input: {
const removeSymlink = Effect.fn("CodexHomeLayout.removeSymlink")(function* (input: {
readonly fileSystem: FileSystem.FileSystem;
readonly sharedHomePath: string;
readonly effectiveHomePath: string;
readonly entryName: string;
}): Effect.fn.Return<void, CodexShadowHomeError, Path.Path> {
const path = yield* Path.Path;
const privatePath = path.join(input.effectiveHomePath, input.entryName);
const entryPath = path.join(input.effectiveHomePath, input.entryName);
const state = yield* readLinkState({
...input,
linkPath: privatePath,
linkPath: entryPath,
});
if (state._tag === "Symlink") {
yield* input.fileSystem.remove(privatePath).pipe(
yield* input.fileSystem.remove(entryPath).pipe(
Effect.catchTags({
PlatformError: (cause) =>
new CodexShadowHomeFileSystemError({
sharedHomePath: input.sharedHomePath,
effectiveHomePath: input.effectiveHomePath,
operation: "remove",
path: privatePath,
path: entryPath,
entryName: input.entryName,
cause,
}),
Expand Down Expand Up @@ -377,12 +383,44 @@ export const materializeCodexShadowHome = Effect.fn("materializeCodexShadowHome"
}
}

const shadowLocalSqliteFamilies = new Set<string>();
for (const entryName of entries) {
const familyBaseName = sqliteFamilyBaseName(entryName);
if (familyBaseName === undefined || shadowLocalSqliteFamilies.has(familyBaseName)) {
continue;
}

const familyBaseState = yield* readLinkState({
fileSystem,
sharedHomePath: layout.sharedHomePath,
effectiveHomePath,
entryName: familyBaseName,
linkPath: path.join(effectiveHomePath, familyBaseName),
});
if (familyBaseState._tag !== "NotSymlink") {
continue;
}

shadowLocalSqliteFamilies.add(familyBaseName);
yield* Effect.forEach(
SQLITE_FAMILY_SUFFIXES,
(suffix) =>
removeSymlink({
fileSystem,
sharedHomePath: layout.sharedHomePath,
effectiveHomePath,
entryName: `${familyBaseName}${suffix}`,
}),
{ discard: true },
);
}

yield* Effect.forEach(
PRIVATE_ENTRY_NAMES,
(entryName) =>
entryName === "auth.json"
? Effect.void
: removePrivateSymlink({
: removeSymlink({
fileSystem,
sharedHomePath: layout.sharedHomePath,
effectiveHomePath,
Expand All @@ -397,6 +435,10 @@ export const materializeCodexShadowHome = Effect.fn("materializeCodexShadowHome"
if (PRIVATE_ENTRY_NAMES.has(entryName)) {
return Effect.void;
}
const familyBaseName = sqliteFamilyBaseName(entryName);
if (familyBaseName !== undefined && shadowLocalSqliteFamilies.has(familyBaseName)) {
return Effect.void;
}
return ensureSymlink({
fileSystem,
sharedHomePath: layout.sharedHomePath,
Expand Down
Loading