From ebaa1bf11b51e560f037b0ba63c575e4a0e5317e Mon Sep 17 00:00:00 2001 From: Simone <185146821+Lucenx9@users.noreply.github.com> Date: Mon, 9 Nov 2026 09:42:47 +0100 Subject: [PATCH 1/3] fix(shared): atomic saves handle long filenames --- packages/shared/src/atomicWrite.test.ts | 8 ++++++-- packages/shared/src/atomicWrite.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/atomicWrite.test.ts b/packages/shared/src/atomicWrite.test.ts index 3b0a99ec0d2d2..5506aeca81e25 100644 --- a/packages/shared/src/atomicWrite.test.ts +++ b/packages/shared/src/atomicWrite.test.ts @@ -44,12 +44,16 @@ it.layer(NodeServices.layer)("writeFileStringAtomically", (it) => { }), ); - it.effect("keeps a symlinked file linked and rewrites its destination", () => + it.effect.each( + HostProcess.Platform.defaultValue() === "linux" + ? ["settings.json", `${"s".repeat(250)}.json`] + : ["settings.json"], + )("keeps a symlinked file linked and rewrites its destination %s", (fileName) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-atomic-write-" }); - const destination = path.join(root, "dotfiles", "settings.json"); + const destination = path.join(root, "dotfiles", fileName); const link = path.join(root, "home", "settings.json"); yield* fs.makeDirectory(path.dirname(destination), { recursive: true }); yield* fs.makeDirectory(path.dirname(link), { recursive: true }); diff --git a/packages/shared/src/atomicWrite.ts b/packages/shared/src/atomicWrite.ts index e48755047398f..5071a462252d4 100644 --- a/packages/shared/src/atomicWrite.ts +++ b/packages/shared/src/atomicWrite.ts @@ -35,7 +35,7 @@ export const writeFileStringAtomically = (input: { const tempDirectory = yield* Effect.acquireRelease( fs.makeTempDirectory({ directory: targetDirectory, - prefix: `${path.basename(targetPath)}.`, + prefix: "t3-write-", }), (directory) => fs.remove(directory, { recursive: true }).pipe(Effect.ignore({ log: true })), ); From 009e9806810e87988b116e139ba6e392daac052f Mon Sep 17 00:00:00 2001 From: Simone <185146821+Lucenx9@users.noreply.github.com> Date: Mon, 9 Nov 2026 09:53:54 +0100 Subject: [PATCH 2/3] test(shared): honor temporary filesystem filename limits --- packages/shared/src/atomicWrite.test.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/atomicWrite.test.ts b/packages/shared/src/atomicWrite.test.ts index 5506aeca81e25..2a82890462880 100644 --- a/packages/shared/src/atomicWrite.test.ts +++ b/packages/shared/src/atomicWrite.test.ts @@ -53,11 +53,26 @@ it.layer(NodeServices.layer)("writeFileStringAtomically", (it) => { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-atomic-write-" }); - const destination = path.join(root, "dotfiles", fileName); + let destination = path.join(root, "dotfiles", fileName); const link = path.join(root, "home", "settings.json"); yield* fs.makeDirectory(path.dirname(destination), { recursive: true }); yield* fs.makeDirectory(path.dirname(link), { recursive: true }); - yield* fs.writeFileString(destination, "before"); + // Temporary mounts can have a smaller filename limit than the requested ASCII name. + for (let length = fileName.length; length > 0; length--) { + destination = path.join(root, "dotfiles", fileName.slice(0, length)); + const created = yield* fs.writeFileString(destination, "before").pipe( + Effect.as(true), + Effect.catchIf( + (error) => + length > 1 && + error.cause instanceof Error && + "code" in error.cause && + error.cause.code === "ENAMETOOLONG", + () => Effect.succeed(false), + ), + ); + if (created) break; + } yield* fs.symlink(destination, link); yield* writeFileStringAtomically({ filePath: link, contents: "after" }); From 2315486ac5dbcfce040bfec6e085d3709dc384ad Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 15:35:04 +0000 Subject: [PATCH 3/3] test(shared): cover long filenames with a plain atomic save Co-Authored-By: Claude Opus 5.5 --- packages/shared/src/atomicWrite.test.ts | 40 +++++++++++-------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/shared/src/atomicWrite.test.ts b/packages/shared/src/atomicWrite.test.ts index e89dea46f2b39..f0e3bc129cf0a 100644 --- a/packages/shared/src/atomicWrite.test.ts +++ b/packages/shared/src/atomicWrite.test.ts @@ -60,35 +60,16 @@ it.layer(NodeServices.layer)("writeFileStringAtomically", (it) => { }), ); - it.effect.each( - HostProcess.Platform.defaultValue() === "linux" - ? ["settings.json", `${"s".repeat(250)}.json`] - : ["settings.json"], - )("keeps a symlinked file linked and rewrites its destination %s", (fileName) => + it.effect("keeps a symlinked file linked and rewrites its destination", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-atomic-write-" }); - let destination = path.join(root, "dotfiles", fileName); + const destination = path.join(root, "dotfiles", "settings.json"); const link = path.join(root, "home", "settings.json"); yield* fs.makeDirectory(path.dirname(destination), { recursive: true }); yield* fs.makeDirectory(path.dirname(link), { recursive: true }); - // Temporary mounts can have a smaller filename limit than the requested ASCII name. - for (let length = fileName.length; length > 0; length--) { - destination = path.join(root, "dotfiles", fileName.slice(0, length)); - const created = yield* fs.writeFileString(destination, "before").pipe( - Effect.as(true), - Effect.catchIf( - (error) => - length > 1 && - error.cause instanceof Error && - "code" in error.cause && - error.cause.code === "ENAMETOOLONG", - () => Effect.succeed(false), - ), - ); - if (created) break; - } + yield* fs.writeFileString(destination, "before"); yield* fs.symlink(destination, link); yield* writeFileStringAtomically({ filePath: link, contents: "after" }); @@ -98,6 +79,21 @@ it.layer(NodeServices.layer)("writeFileStringAtomically", (it) => { }), ); + it.effect.each(HostProcess.Platform.defaultValue() === "linux" ? [255] : [])( + "saves a file whose name is %i bytes long", + (length) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-atomic-write-" }); + const filePath = path.join(root, `${"s".repeat(length - 5)}.json`); + + yield* writeFileStringAtomically({ filePath, contents: "after" }); + + assert.strictEqual(yield* fs.readFileString(filePath), "after"); + }), + ); + it.effect("keeps a dangling symlink linked and creates its destination", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem;