From 5602576ae1a9091e89bc2a3b65a0449857ec3373 Mon Sep 17 00:00:00 2001 From: dotta Date: Tue, 24 Mar 2026 08:03:04 -0500 Subject: [PATCH 1/4] Fix embedded Postgres initdb failure in Docker slim containers The embedded-postgres library hardcodes --lc-messages=en_US.UTF-8 and strips the parent process environment when spawning initdb/postgres. In slim Docker images (e.g. node:20-bookworm-slim), the en_US.UTF-8 locale isn't installed, causing initdb to exit with code 1. Two fixes applied: 1. Add --lc-messages=C to all initdbFlags arrays (overrides the library's hardcoded locale since our flags come after in the spread) 2. pnpm patch on embedded-postgres to preserve process.env in spawn calls, preventing loss of PATH, LD_LIBRARY_PATH, and other vars Co-Authored-By: Paperclip --- .../company-import-export-e2e.test.ts | 2 +- cli/src/commands/worktree.ts | 2 +- package.json | 9 ++++++-- packages/db/src/client.test.ts | 2 +- packages/db/src/migration-runtime.ts | 2 +- .../embedded-postgres@18.1.0-beta.16.patch | 22 +++++++++++++++++++ .../heartbeat-process-recovery.test.ts | 2 +- server/src/__tests__/issues-service.test.ts | 2 +- server/src/__tests__/routines-e2e.test.ts | 2 +- server/src/__tests__/routines-service.test.ts | 2 +- server/src/index.ts | 2 +- 11 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 patches/embedded-postgres@18.1.0-beta.16.patch diff --git a/cli/src/__tests__/company-import-export-e2e.test.ts b/cli/src/__tests__/company-import-export-e2e.test.ts index 82f1f1ca..27334105 100644 --- a/cli/src/__tests__/company-import-export-e2e.test.ts +++ b/cli/src/__tests__/company-import-export-e2e.test.ts @@ -63,7 +63,7 @@ async function startTempDatabase() { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/cli/src/commands/worktree.ts b/cli/src/commands/worktree.ts index 877f6bdd..7a2bd127 100644 --- a/cli/src/commands/worktree.ts +++ b/cli/src/commands/worktree.ts @@ -756,7 +756,7 @@ async function ensureEmbeddedPostgres(dataDir: string, preferredPort: number): P password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/package.json b/package.json index 0f5c23ad..749cc8d0 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "test:release-smoke:headed": "npx playwright test --config tests/release-smoke/playwright.config.ts --headed" }, "devDependencies": { - "cross-env": "^10.1.0", "@playwright/test": "^1.58.2", + "cross-env": "^10.1.0", "esbuild": "^0.27.3", "typescript": "^5.7.3", "vitest": "^3.0.5" @@ -44,5 +44,10 @@ "engines": { "node": ">=20" }, - "packageManager": "pnpm@9.15.4" + "packageManager": "pnpm@9.15.4", + "pnpm": { + "patchedDependencies": { + "embedded-postgres@18.1.0-beta.16": "patches/embedded-postgres@18.1.0-beta.16.patch" + } + } } diff --git a/packages/db/src/client.test.ts b/packages/db/src/client.test.ts index 9a86a7b3..752fce15 100644 --- a/packages/db/src/client.test.ts +++ b/packages/db/src/client.test.ts @@ -67,7 +67,7 @@ async function createTempDatabase(): Promise { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/packages/db/src/migration-runtime.ts b/packages/db/src/migration-runtime.ts index 3b5921b1..921de612 100644 --- a/packages/db/src/migration-runtime.ts +++ b/packages/db/src/migration-runtime.ts @@ -150,7 +150,7 @@ async function ensureEmbeddedPostgresConnection( password: "paperclip", port: selectedPort, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/patches/embedded-postgres@18.1.0-beta.16.patch b/patches/embedded-postgres@18.1.0-beta.16.patch new file mode 100644 index 00000000..0f25ff39 --- /dev/null +++ b/patches/embedded-postgres@18.1.0-beta.16.patch @@ -0,0 +1,22 @@ +diff --git a/dist/index.js b/dist/index.js +index ccfe17a82f4879bf20cc345c579a987d9eba5309..dd689f5908f625f49b4785318daea736aa88927f 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -133,7 +133,7 @@ class EmbeddedPostgres { + `--pwfile=${passwordFile}`, + `--lc-messages=${LC_MESSAGES_LOCALE}`, + ...this.options.initdbFlags, +- ], Object.assign(Object.assign({}, permissionIds), { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } })); ++ ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); + // Connect to stderr, as that is where the messages get sent + (_a = process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => { + // Parse the data as a string and log it +@@ -177,7 +177,7 @@ class EmbeddedPostgres { + '-p', + this.options.port.toString(), + ...this.options.postgresFlags, +- ], Object.assign(Object.assign({}, permissionIds), { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } })); ++ ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); + // Connect to stderr, as that is where the messages get sent + (_a = this.process.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => { + // Parse the data as a string and log it diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index a5742f42..d0e3cc31 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -72,7 +72,7 @@ async function startTempDatabase() { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 70b99ba8..ba27866f 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -68,7 +68,7 @@ async function startTempDatabase() { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/server/src/__tests__/routines-e2e.test.ts b/server/src/__tests__/routines-e2e.test.ts index 301f045f..83689724 100644 --- a/server/src/__tests__/routines-e2e.test.ts +++ b/server/src/__tests__/routines-e2e.test.ts @@ -130,7 +130,7 @@ async function startTempDatabase() { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/server/src/__tests__/routines-service.test.ts b/server/src/__tests__/routines-service.test.ts index ee2e261e..d5954246 100644 --- a/server/src/__tests__/routines-service.test.ts +++ b/server/src/__tests__/routines-service.test.ts @@ -76,7 +76,7 @@ async function startTempDatabase() { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: () => {}, onError: () => {}, }); diff --git a/server/src/index.ts b/server/src/index.ts index eb0964ee..d47d6674 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -347,7 +347,7 @@ export async function startServer(): Promise { password: "paperclip", port, persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], onLog: appendEmbeddedPostgresLog, onError: appendEmbeddedPostgresLog, }); From 4ff460de38db506150b290f4e4b9fc5a9d4cc204 Mon Sep 17 00:00:00 2001 From: dotta Date: Tue, 24 Mar 2026 11:51:45 -0500 Subject: [PATCH 2/4] Fix embedded-postgres patch env lookup Use globalThis.process.env in the vendor patch so the spawned child process config does not trip over the local process binding inside embedded-postgres. Co-Authored-By: Paperclip --- patches/embedded-postgres@18.1.0-beta.16.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patches/embedded-postgres@18.1.0-beta.16.patch b/patches/embedded-postgres@18.1.0-beta.16.patch index 0f25ff39..99c2f911 100644 --- a/patches/embedded-postgres@18.1.0-beta.16.patch +++ b/patches/embedded-postgres@18.1.0-beta.16.patch @@ -7,7 +7,7 @@ index ccfe17a82f4879bf20cc345c579a987d9eba5309..dd689f5908f625f49b4785318daea736 `--lc-messages=${LC_MESSAGES_LOCALE}`, ...this.options.initdbFlags, - ], Object.assign(Object.assign({}, permissionIds), { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } })); -+ ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); ++ ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, globalThis.process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); // Connect to stderr, as that is where the messages get sent (_a = process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => { // Parse the data as a string and log it @@ -16,7 +16,7 @@ index ccfe17a82f4879bf20cc345c579a987d9eba5309..dd689f5908f625f49b4785318daea736 this.options.port.toString(), ...this.options.postgresFlags, - ], Object.assign(Object.assign({}, permissionIds), { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } })); -+ ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); ++ ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, globalThis.process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); // Connect to stderr, as that is where the messages get sent (_a = this.process.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => { // Parse the data as a string and log it From f352f3f5147e80cea9f9974a370d13039e29c515 Mon Sep 17 00:00:00 2001 From: dotta Date: Tue, 24 Mar 2026 11:55:39 -0500 Subject: [PATCH 3/4] Force embedded-postgres messages locale to C The vendor package still hardcoded LC_MESSAGES to en_US.UTF-8. That locale is missing in slim containers, and initdb fails during bootstrap even when --lc-messages=C is passed later. Co-Authored-By: Paperclip --- patches/embedded-postgres@18.1.0-beta.16.patch | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/patches/embedded-postgres@18.1.0-beta.16.patch b/patches/embedded-postgres@18.1.0-beta.16.patch index 99c2f911..f99088e7 100644 --- a/patches/embedded-postgres@18.1.0-beta.16.patch +++ b/patches/embedded-postgres@18.1.0-beta.16.patch @@ -2,9 +2,19 @@ diff --git a/dist/index.js b/dist/index.js index ccfe17a82f4879bf20cc345c579a987d9eba5309..dd689f5908f625f49b4785318daea736aa88927f 100644 --- a/dist/index.js +++ b/dist/index.js +@@ -24,7 +24,7 @@ const { Client } = pg; + * output of the `initdb` command to see if Postgres is ready. As we're looking + * for a particular string, we need to force that string into the right locale. + * @see https://github.com/leinelissen/embedded-postgres/issues/15 + */ +-const LC_MESSAGES_LOCALE = 'en_US.UTF-8'; ++const LC_MESSAGES_LOCALE = 'C'; + // The default configuration options for the class + const defaults = { + databaseDir: path.join(process.cwd(), 'data', 'db'), @@ -133,7 +133,7 @@ class EmbeddedPostgres { - `--pwfile=${passwordFile}`, - `--lc-messages=${LC_MESSAGES_LOCALE}`, + `--pwfile=${passwordFile}`, + `--lc-messages=${LC_MESSAGES_LOCALE}`, ...this.options.initdbFlags, - ], Object.assign(Object.assign({}, permissionIds), { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } })); + ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, globalThis.process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); From 4b668379bc07ecd9f226c971b6a8cdab66b26a14 Mon Sep 17 00:00:00 2001 From: dotta Date: Tue, 24 Mar 2026 11:56:41 -0500 Subject: [PATCH 4/4] Regenerate embedded-postgres vendor patch Rebuild the patch file with valid unified-diff hunks so pnpm can apply the locale and environment fixes during install. Co-Authored-By: Paperclip --- patches/embedded-postgres@18.1.0-beta.16.patch | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/patches/embedded-postgres@18.1.0-beta.16.patch b/patches/embedded-postgres@18.1.0-beta.16.patch index f99088e7..0030bd99 100644 --- a/patches/embedded-postgres@18.1.0-beta.16.patch +++ b/patches/embedded-postgres@18.1.0-beta.16.patch @@ -1,9 +1,7 @@ diff --git a/dist/index.js b/dist/index.js -index ccfe17a82f4879bf20cc345c579a987d9eba5309..dd689f5908f625f49b4785318daea736aa88927f 100644 --- a/dist/index.js +++ b/dist/index.js -@@ -24,7 +24,7 @@ const { Client } = pg; - * output of the `initdb` command to see if Postgres is ready. As we're looking +@@ -23,7 +23,7 @@ * for a particular string, we need to force that string into the right locale. * @see https://github.com/leinelissen/embedded-postgres/issues/15 */ @@ -12,16 +10,16 @@ index ccfe17a82f4879bf20cc345c579a987d9eba5309..dd689f5908f625f49b4785318daea736 // The default configuration options for the class const defaults = { databaseDir: path.join(process.cwd(), 'data', 'db'), -@@ -133,7 +133,7 @@ class EmbeddedPostgres { - `--pwfile=${passwordFile}`, - `--lc-messages=${LC_MESSAGES_LOCALE}`, +@@ -133,7 +133,7 @@ + `--pwfile=${passwordFile}`, + `--lc-messages=${LC_MESSAGES_LOCALE}`, ...this.options.initdbFlags, - ], Object.assign(Object.assign({}, permissionIds), { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } })); + ], Object.assign(Object.assign({}, permissionIds), { env: Object.assign(Object.assign({}, globalThis.process.env), { LC_MESSAGES: LC_MESSAGES_LOCALE }) })); // Connect to stderr, as that is where the messages get sent (_a = process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => { // Parse the data as a string and log it -@@ -177,7 +177,7 @@ class EmbeddedPostgres { +@@ -177,7 +177,7 @@ '-p', this.options.port.toString(), ...this.options.postgresFlags,