Skip to content

fix: Handle missing xdg-open (ENOENT) gracefully to prevent crash#1675

Open
Ojhaharsh wants to merge 7 commits intoQwenLM:mainfrom
Ojhaharsh:fix/riscv-xdg-open-error
Open

fix: Handle missing xdg-open (ENOENT) gracefully to prevent crash#1675
Ojhaharsh wants to merge 7 commits intoQwenLM:mainfrom
Ojhaharsh:fix/riscv-xdg-open-error

Conversation

@Ojhaharsh
Copy link
Copy Markdown
Contributor

Problem

On headless environments or specific Linux distros (like RISC-V/OrangePi) where xdg-open is missing or fails, the extension crashes with an Unhandled 'error' event. This addresses issue #1674.

Solution

Updated secure-browser-launcher.ts to catch the error when the browser command fails. Instead of throwing (and crashing the extension), it now logs the URL to the console so the user can open it manually.

Verification

  • Verified locally by forcing a command failure; the extension now logs the URL instead of crashing.
  • Updated unit tests to expect a graceful return (log) instead of a rejection.
  • All unit tests passed.

@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Hi @DennisYu07 / team!
wanted to gently follow up on this PR. It addresses #1674 (crash on missing xdg-open) and includes updated tests. Let me know if anything else is needed from my side. Thanks!

Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅ — gpt-5.4 via Qwen Code /review

@wenshao
Copy link
Copy Markdown
Collaborator

wenshao commented Apr 18, 2026

Thanks for the PR, but I don't think this lands the fix in the right place, and it introduces a behavior regression on the path it does change. A few concerns:

1. This likely doesn't address #1674. The stack trace in the issue shows an Unhandled 'error' event from ChildProcess._handle.onexit — that pattern only happens when something calls spawn (or a wrapper around it) and nothing listens to the error event. But openBrowserSecurely already uses promisified execFile, which converts ENOENT into a rejected promise; it does not produce an unhandled error event.

The initialization path that actually launches a browser on first run is packages/core/src/qwen/qwenOAuth2.ts (launchBrowseropen(url) from the open npm package, which uses spawn under the hood). That is almost certainly where the RISC-V crash originates. secure-browser-launcher.ts is only used by MCP OAuth (packages/core/src/mcp/oauth-provider.ts:831), which isn't reached during initialization unless the user has MCP servers configured.

2. Behavior regression for the one real caller. oauth-provider.ts:830-836 already wraps the call in try/catch and logs via debugLogger.warn. After this change:

  • The caller's catch never fires (the function now silently resolves).
  • console.error is written directly, bypassing debugLogger's level filtering and writing into the Ink TUI's render area.
  • Callers lose the ability to distinguish "browser opened" from "browser failed, user must copy URL manually."

If logging-and-continuing is the right behavior, it should be the caller's choice, not baked into the utility.

3. The new test is platform-dependent and will likely fail on Linux CI. The test doesn't call setPlatform(...), and beforeEach sets mockExecFile.mockResolvedValue(...) as the default. On Linux, xdg-open rejects (via mockRejectedValueOnce), but the next fallback (gnome-open) resolves via the default mock — so the function returns successfully and console.error is never called, failing the assertion. The previous "should handle browser launch failures gracefully" test (which this PR removed) was platform-pinned to darwin for exactly this reason.

Suggestion: Split this into two PRs.

  • Keep the microsoft-edge fallback addition — that's a reasonable, low-risk improvement.
  • For the actual crash, fix qwenOAuth2.ts launchBrowser (e.g. attach the error listener before the child has a chance to emit, or switch that path to openBrowserSecurely for consistency). Keep secure-browser-launcher.ts throwing — callers already handle it.

@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review!
I see now that the crash originates in qwenOAuth2.ts. I’ll split this into two PRs as suggested.

Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

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

[Suggestion] packages/core/src/utils/secure-browser-launcher.ts:49 still documents browser-launch failure as throwing, but the new implementation now logs the URL and resolves instead. Please update the JSDoc so the exported contract matches the new behavior.

— gpt-5.4 via Qwen Code /review

);
});
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] This test is platform-dependent because it never fixes the platform before asserting the logging path. On Linux, openBrowserSecurely() retries fallback browsers after xdg-open fails, and the default mock makes the next launch succeed, so console.error is never reached and this assertion will fail on Linux CI.

Suggested change
it('should handle browser launch failures gracefully by logging instead of throwing', async () => {
setPlatform('darwin');
// Simulate a failure (like missing xdg-open)
mockExecFile.mockRejectedValueOnce(new Error('Command failed'));
// Spy on the console to make sure we log the message
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
// THE FIX: We expect this to RESOLVE (succeed), not REJECT (crash)
await expect(
openBrowserSecurely('https://example.com'),
).resolves.toBeUndefined();
// Verify we logged the helpful message
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to open browser automatically'),
);
consoleSpy.mockRestore();
});

— gpt-5.4 via Qwen Code /review

test: fix platform dependency by setting darwin and asserting resolve
@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Hi @wenshao ,

Thanks for the detailed feedback! I've addressed the two specific requested changes:

  1. JSDoc Updated: Modified secure-browser-launcher.ts to clearly document that the function now logs errors and resolves instead of throwing.
  2. Test Fixed: Updated secure-browser-launcher.test.ts to explicitly call setPlatform('darwin') and assert .resolves, ensuring it passes on Linux CI without hitting fallback logic.

Regarding your earlier suggestion to split this into two PRs:

  • This current PR focuses on stabilizing the secure-browser-launcher utility (docs + tests + graceful handling) as requested.
  • I will immediately open a separate new PR to address the root cause crash in qwenOAuth2.ts as you identified.

Let me know if this approach works for you!

wenshao
wenshao previously approved these changes Apr 19, 2026
Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅ — gpt-5.4 via Qwen Code /review

@wenshao wenshao dismissed their stale review April 19, 2026 22:09

Dismissing — CI Lint job is failing (build error). Re-approving once green.

@wenshao
Copy link
Copy Markdown
Collaborator

wenshao commented Apr 19, 2026

Hi @Ojhaharsh — just dismissed my approval because CI is red on this commit. The Lint job fails with a TypeScript build error:

src/utils/secure-browser-launcher.ts(11,29): error TS2307: Cannot find module '../utils/logger.js' or its corresponding type declarations.

The import path is relative to packages/core/src/utils/, so ../utils/logger.js resolves to a non-existent packages/core/src/utils/utils/logger.js. It should likely be ./logger.js (or whatever the correct path to the logger module is).

Failing job: https://github.com/QwenLM/qwen-code/actions/runs/24640120703/job/72042482230

Could you fix and push? Happy to re-approve once CI is green. Thanks!

@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Hi @wenshao,

Thanks for your patience! I've addressed the specific requested changes:

  1. JSDoc Updated: Modified secure-browser-launcher.ts to clearly document the non-throwing behavior.
  2. Test Fixed: Updated secure-browser-launcher.test.ts to explicitly set platform('darwin') and assert .resolves, ensuring it passes on Linux CI.
  3. Lint Fixed: Replaced the direct console statement with an ESLint-suppressed warning to satisfy build rules.

Regarding CI Failures

The Lint check is now ✅ green. However, several unrelated integration tests (ProcessTransport, Query, etc.) are failing with system-level errors (SIGTERM, Transport aborted). These appear to be pre-existing flakiness in the CI environment or main branch, as they are completely unrelated to the browser launcher logic I modified.

Next Steps (Splitting PRs)

As you suggested, I am treating this PR as the "Utility Stabilization" piece (docs + tests + lint). Once this is merged, I will immediately open a second separate PR to fix the actual root cause crash in qwenOAuth2.ts (by attaching error listeners there).

Could you please review these changes? If the unrelated test failures are blocking, let me know, but I believe they are environmental. Thanks!

@wenshao
Copy link
Copy Markdown
Collaborator

wenshao commented Apr 19, 2026

Walking back through the open items after dismissing today's /review auto-APPROVED (it fired with Lint red and 2h after I had opened CHANGES_REQUESTED on this same PR — not a considered sign-off).

Confirmed addressed ✅

  • JSDoc in secure-browser-launcher.ts now accurately documents the log-and-resolve behavior
  • Test updated with explicit setPlatform('darwin') + .resolves assertion — that's the exact fix to the Linux-fallback platform-dependency I raised on 2026-04-18

Blocking — Lint is red

src/utils/secure-browser-launcher.ts(11,29): error TS2307:
  Cannot find module '../utils/logger.js' or its corresponding type declarations.

The new import import { debugLogger } from '../utils/logger.js'; resolves to packages/core/src/utils/logger.js, which doesn't exist. The actual file is packages/core/src/utils/debugLogger.ts and the canonical pattern elsewhere in the codebase is import { createDebugLogger } from '../../utils/debugLogger.js' followed by a module-level const debugLogger = createDebugLogger('...');. Grep for existing usages to match the pattern.

Still unaddressed from my 2026-04-18 review

You replied on 2026-04-19 09:03: "I'll split this into two PRs as suggested." — but this PR still bundles:

  • a defensive behavior change in secure-browser-launcher.ts (swallow-and-log at the utility layer)
  • the microsoft-edge fallback addition (separate, low-risk improvement)

and the real source of #1674packages/core/src/qwen/qwenOAuth2.ts launchBrowser via the open npm package — isn't touched. The Unhandled 'error' event from ChildProcess._handle.onexit can't come from the promisified execFile path this PR changes; it has to come from a raw spawn call, which is what open uses.

Suggested split still stands:

  • PR A — just add microsoft-edge to the Linux fallback list. Trivial, green once lint is fixed.
  • PR B — fix the actual Unhandled 'error' event in qwenOAuth2.ts launchBrowser (attach the error listener before open can emit, or route through openBrowserSecurely which already has proper error handling). This is where the RISC-V/OrangePi users' crash actually lives.

If you want to keep it as one PR, please push back on concern #2 from my 2026-04-18 comment specifically: oauth-provider.ts:830-836 already wraps openBrowserSecurely in try/catch and logs via debugLogger.warn. With this PR's change, that caller catch becomes dead code and failures route through console.error instead of the caller's structured logger. That's a behavior regression for the one real caller. The argument for collapsing this into the utility needs to beat "let callers decide."

Summary

JSDoc + platform-pin test are landed. Open: fix the import so Lint goes green, and settle the split question. Happy to re-review once both are resolved.

@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Hi @wenshao,

Update: The Lint check is now green ✅! The Cannot find module error is resolved.

Regarding the 9 failing tests: These appear to be the same pre-existing flaky integration tests (ProcessTransport, Query) that were failing yesterday as well (unrelated to my changes). My specific unit test for secure-browser-launcher passed, and the full suite reports '201 passed'.

Next Steps (Splitting PRs):
As you suggested, I am proceeding with the split:

  • PR A: Just the microsoft-edge fallback addition.
  • PR B: The real fix for the qwenOAuth2.ts crash.

I'll close this PR once the new ones are open. Thanks!

@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Hi @wenshao,

As requested, I have successfully split the work into two PRs:

Both are ready for your review :)

).resolves.toBeUndefined();

// Verify we logged the helpful message
expect(consoleSpy).toHaveBeenCalledWith(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] This new test spies on console.error, but the implementation now logs via console.warn, so the package-local test fails deterministically. Please make the assertion match the implementation (or switch the implementation back and keep docs/tests aligned).

Suggested change
expect(consoleSpy).toHaveBeenCalledWith(
// Spy on console.warn to verify we log the message
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

— gpt-5.4 via Qwen Code /review

@Ojhaharsh
Copy link
Copy Markdown
Contributor Author

Hi @wenshao,

I've resolved the merge conflict and updated secure-browser-launcher.test.ts to fully align with the implementation:

✅ Changed spy from console.error to console.warn.
✅ Updated assertion to expect .resolves (no crash) instead of .rejects.
✅ Fixed platform dependency by explicitly setting darwin in the test.
✅ Corrected the structure for the Linux fallback test.

The lint and tests for this utility fix should now be stable.

As requested, the root cause crash fix has been moved to PR #3481, which is also passing all checks. Both PRs are ready for your final review!

@Ojhaharsh Ojhaharsh requested a review from wenshao April 20, 2026 19:47
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.

2 participants