From c0b1e1b8212ffa4280c1ec8bead71dbe5a0a7b41 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 11 Jul 2026 03:47:10 -0700 Subject: [PATCH] fix(selfhost): gate the queue consumer on env readiness at boot Both queue backends self-heal a foreground job left over-deferred across a restart by releasing it and kicking the pump once at boot, inside queue construction/init() itself. That can invoke consume() before `env` is assigned further down in main(), dereferencing undefined and surfacing as a misleading generic job_error right after a container restart. consume() now awaits an envReady promise resolved once env is fully assigned. Closes #5049 --- src/server.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/server.ts b/src/server.ts index 42c1bff218..00a7a7deda 100644 --- a/src/server.ts +++ b/src/server.ts @@ -334,10 +334,21 @@ async function main(): Promise { /* v8 ignore stop */ const startedAt = Date.now(); - // The queue consumer captures `env`, assigned below (the first job only runs once an HTTP/cron event - // arrives, by which point env is set). + // The queue consumer captures `env`, assigned further below once the backend/migrations/AI providers are + // ready. That used to rest on "the first job only runs once an HTTP/cron event arrives, by which point env + // is set" -- false: both queue backends self-heal any foreground job left over-deferred across a restart by + // releasing it and kicking the pump ONCE at boot, inside queue construction/init() itself (see + // releaseStaleForegroundDeferrals in pg-queue.ts/sqlite-queue.ts), which can invoke consume() well before + // `env` below is assigned -- surfacing as a misleading generic job_error ("Cannot read properties of + // undefined") right after a container restart whenever a foreground job happened to be sitting deferred at + // that moment. Gate on envReady so a boot-time release waits for `env` instead of dereferencing it early. let env: Env; + let markEnvReady!: () => void; + const envReady = new Promise((resolve) => { + markEnvReady = resolve; + }); const consume = async (message: JobMessage): Promise => { + await envReady; try { await processJob(env, message); } catch (error) { @@ -637,6 +648,7 @@ async function main(): Promise { return binding ? { REVIEW_AUDIT: binding } : {}; })(), } as unknown as Env; + markEnvReady(); // GitHub App auth: a successful JWT mint proves GITHUB_APP_PRIVATE_KEY is set and parses as a valid signing // key. Without this, an invalid/expired key leaves the review pipeline completely dead while /ready still