Skip to content

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

Merged
wenshao merged 7 commits into
QwenLM:mainfrom
Ojhaharsh:fix/riscv-xdg-open-error
Apr 21, 2026
Merged

fix: Handle missing xdg-open (ENOENT) gracefully to prevent crash#1675
wenshao merged 7 commits into
QwenLM: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!

@wenshao wenshao left a comment

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.

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

@wenshao

wenshao commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator

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.

@wenshao wenshao left a comment

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.

[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

@wenshao wenshao left a comment

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.

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

wenshao commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator

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

wenshao commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator

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

@wenshao wenshao left a comment

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.

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

@wenshao

wenshao commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Verified locally on Linux at commit 64d70d9f:

  • secure-browser-launcher.test.ts: 14/14 pass
  • oauth-provider.test.ts (only real caller): 21/21 pass, no regression
  • ENOENT smoke test (PATH='' + openBrowserSecurely('https://example.com')): walks the full fallback chain, logs Failed to open browser automatically. Please open this URL manually: <url> to stderr, resolves without crashing

Items from my 2026-04-18 review are addressed:

  • JSDoc matches the new log-and-resolve contract
  • Test platform-pinned to darwin; Linux fallback moved to its own describe block
  • Lint green

Minor nits (not blocking — happy to clean up in a follow-up):

  • secure-browser-launcher.ts:139 /* eslint-disable no-console */ is unused (eslint reports Unused eslint-disable directive); console.warn isn't restricted at that position
  • Both files are missing a trailing newline
  • oauth-provider.ts:758-765 still wraps this call in try/catch with its own console.warn; after this change that catch is dead code for the browser-launch path. User doesn't lose info (the URL is already printed as a TIP above), but worth simplifying later.

Note for anyone arriving from #1674: this PR on its own does not fix that crash. The unhandled 'error' event reported there comes from the open npm package's raw spawn in qwenOAuth2.ts, not from this utility (which uses promisify(execFile) and already rejects cleanly on ENOENT). The root-cause fix lives in #3481, still under review.

Squash-merging now.

@wenshao wenshao merged commit ece5139 into QwenLM:main Apr 21, 2026
13 checks passed
@Ojhaharsh Ojhaharsh deleted the fix/riscv-xdg-open-error branch April 21, 2026 21:44
chiga0 pushed a commit that referenced this pull request Apr 24, 2026
…her (#1675)

- secure-browser-launcher now logs the URL and resolves instead of throwing
  when all browser commands fail, preventing callers from crashing on
  headless or minimal Linux environments
- Add microsoft-edge to the Linux fallback list
- Update JSDoc to match the new log-and-resolve contract
- Pin test platform to darwin so the assertion doesn't get masked by the
  Linux fallback chain

Note: this PR stabilizes the utility but does not by itself fix the
RISC-V/OrangePi crash in #1674, which originates from the `open` npm
package in qwenOAuth2.ts. See #3481 for the root-cause fix.
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…her (QwenLM#1675)

- secure-browser-launcher now logs the URL and resolves instead of throwing
  when all browser commands fail, preventing callers from crashing on
  headless or minimal Linux environments
- Add microsoft-edge to the Linux fallback list
- Update JSDoc to match the new log-and-resolve contract
- Pin test platform to darwin so the assertion doesn't get masked by the
  Linux fallback chain

Note: this PR stabilizes the utility but does not by itself fix the
RISC-V/OrangePi crash in QwenLM#1674, which originates from the `open` npm
package in qwenOAuth2.ts. See QwenLM#3481 for the root-cause fix.
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