Skip to content

fix: replace linux-distro with direct /etc/os-release parsing (#2236)#2308

Open
caddydove wants to merge 1 commit into
anyproto:developfrom
caddydove:fix/linux-distro-cjs-esm-interop
Open

fix: replace linux-distro with direct /etc/os-release parsing (#2236)#2308
caddydove wants to merge 1 commit into
anyproto:developfrom
caddydove:fix/linux-distro-cjs-esm-interop

Conversation

@caddydove

@caddydove caddydove commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #2236 — replaces the linux-distro npm dependency with a direct read of /etc/os-release, eliminating the fragile execa CJS/ESM interop chain that caused TypeError: execa is not a function on Linux.

What changed

Before: require('linux-distro') which internally called require('execa'). Since the bundled execa is ESM-only (v7), the CJS require resolved a module namespace object instead of a callable function, crashing at the call site on line 11 of linux-distro/index.js.

After: Read /etc/os-release (or /usr/lib/os-release / /etc/lsb-release as fallbacks) directly with fs.readFileSync. Parse the KEY=value format and return the { os, name, release, code } shape that analytics.ts already expects.

Why not the previous approach

The earlier commit switched from require() to dynamic import(). This didn't fix the actual problem — linux-distro@4 is still a CJS module, and its internal require('execa') resolves identically regardless of how we import it from the outside. The try/catch only masked the crash while returning a wrong-shaped fallback. This version removes the dependency entirely.

Verification

  • analytics.ts reads data.os and data.release — both are now populated from /etc/os-release
  • esbuild compilation succeeds
  • On non-Linux or missing os-release files, returns { os: 'Linux', name: 'Unknown', release: '', code: '' }

@requilence
requilence self-requested a review July 21, 2026 17:21

@requilence requilence left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for chasing down the Linux login crash — the crash guard is definitely worth landing. But the diagnosis and the credited mechanism aren't right, and I'd like the framing (and ideally the approach) adjusted before merge.

What actually fixes the crash

It's the try/catch, not the require → import switch. linux-distro@4 is CommonJS and internally does const execa = require('execa'), then calls execa('lsb_release', …) at line 11 (the crash site). How we load linux-distro from the outside doesn't change its own internal require('execa') — a dynamic import() of a CJS module still runs it through the CJS loader, and that internal require('execa') resolves identically. So if it was resolving the ESM execa@7 namespace (non-callable → execa is not a function) before, it still is after this change. The only reason the app stops crashing is that the synchronous throw is now caught.

Why this masks the bug rather than fixing it

With the current patch, linuxDistro() silently returns Unknown/Unknown on every affected build, and analytics.ts (data.os / data.release) then reports blank distro info. That's better than crashing, but it's graceful degradation, not restored detection.

Real root cause

An execa version mismatch: linux-distro@4 expects execa@^0.2.x (CJS, default-callable) but in the crashing environment resolves the hoisted execa@7 (ESM, named exports only). Neither import() nor the try/catch addresses that.

Suggested approach

  • Keep the try/catch (good defensive guard) — but rewrite the PR description to credit it, and drop or clearly mark the require → import change as incidental.
  • Actually fix detection with one of: ensure linux-distro resolves a CJS execa@0.2.x (nest/pin it), or — simplest and dependency-free — replace linux-distro with a direct read of /etc/os-release (ID / VERSION_ID), which removes the fragile execa chain entirely.
  • Make the fallback shape match what the consumer reads ({ os, name, release, code }) so the degraded value is consistent instead of undefined.
  • Smoke-test on a real dist:linux build to confirm esbuild preserves import() and the main process executes it.

Happy to help with the /etc/os-release version if useful.

@requilence

Copy link
Copy Markdown
Contributor

One more clarification on severity: this isn't actually an app crash — the IPC rejection is already caught in preload.cjs (.catch) and swallowed in analytics.ts (if (!data) return), so it's a benign stderr log line on Linux (confirmed by the #2236 reporter who logs in fine with the same error); the only real effect is that distro analytics silently go unrecorded.

@caddydove

Copy link
Copy Markdown
Contributor Author

You’re right, I conflated preventing the crash with actually restoring distro detection. Dynamic import doesn’t change how linux-distro resolves its internal require('execa'), so the current patch only catches the failure and degrades gracefully. Thanks for also catching the return shape mismatch. I’ll rework this to read /etc/os-release directly, keep a defensive fallback matching the consumer’s expected shape, update the PR description, and smoke-test the Linux build.

@caddydove caddydove changed the title Fix linux-distro CJS/ESM interop crash on Linux login (#2236) fix: replace linux-distro with direct /etc/os-release parsing (#2236) Jul 22, 2026
…to#2236)

Replace linux-distro dependency with direct /etc/os-release reading
via fs.readFileSync. linux-distro@4 internally uses require('execa'),
but the bundled execa is ESM-only (v7), causing TypeError at runtime.

Switching from require() to dynamic import() doesn't fix this — the
CJS module still resolves execa via CJS require internally. Removing
linux-distro entirely eliminates the fragile execa dependency chain.

Also fixes the return type to match the actual shape expected by
analytics.ts consumer: { os, name, release, code } instead of
the incorrect { name, version }.
@caddydove
caddydove force-pushed the fix/linux-distro-cjs-esm-interop branch from 252cef8 to bc48a01 Compare July 22, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App crashes at login with TypeError: execa is not a function in linux-distro, affects all recent versinos

2 participants