Skip to content

Connect: Stop execution of main process on child process spawn error#56683

Merged
ravicious merged 4 commits intomasterfrom
r7s/main-process-err
Jul 23, 2025
Merged

Connect: Stop execution of main process on child process spawn error#56683
ravicious merged 4 commits intomasterfrom
r7s/main-process-err

Conversation

@ravicious
Copy link
Copy Markdown
Member

@ravicious ravicious commented Jul 10, 2025

Closes #56272

This PR makes it so that when tshd or the shared process fails to spawn, we show a dialog box with a relevant error message rather than with "Cannot read properties of undefined (reading 'then')", as shown in #56272. It makes it so that when a user sends a screenshot of the dialog, we can identify the process which caused the problem instead of having to ask the user for logs.

I posted an RCA under the issue (#56272 (comment)) if you'd like to learn more about the problem. In short, we thought that Electron's app.exit(1) immediately stops the execution of a program, but that is not the case. On top of that, macOS and Windows behave differently when attempting to spawn a child process (this is described below in the Screenshots section), so the original issue could be reproduced only on Windows.

Screenshots

The screenshots both show the error that is shown after trying to start the dev version of the app with README.md as the tsh binary.

E_DO_NOT_USE=1 CONNECT_TSH_BIN_PATH=$PWD/README.md pnpm start-term

(E_DO_NOT_USE is just so that I don't execute this by mistake when going through my shell history)

On Windows, child_process.spawn throws a sync error. The error message doesn't indicate which process failed to spawn, but the stacktrace does include this information (MainProcess.initTshd).

windows-spawn-error

On macOS, the error isn't thrown immediately on spawn, rather it's emitted with the error event on a process. Thus the title of the dialog is different as the error is surfaced later in the initialization process. On macOS, the error dialog content is unfortunately centered, so the stacktrace looks a bit weird. We're forced to use it though, as according to the docs of dialog.showErrorBox:

This API can be called safely before the ready event the app module emits, it is usually used to report errors in early stage of startup.

macos-spawn-error

@ravicious ravicious added the no-changelog Indicates that a PR does not require a changelog entry label Jul 10, 2025
@ravicious ravicious force-pushed the r7s/main-process-err branch 3 times, most recently from 8491137 to 929d4d2 Compare July 22, 2025 08:55
Comment thread web/packages/teleterm/src/main.ts Outdated
} catch (error) {
const message = 'Could not initialize the main process';
logger.error(message, error);
dialog.showErrorBox(message, ensureError(error).message);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I had a TODO item that said:

Investigate why the code from this branch actually doesn't show any error dialog box when tsh cannot start. The error is logged and the process remains in the background but no dialog error is shown, indicating that the app is blocked on the code that's supposed to show the dialog.

After starting the app in dev mode, I discovered this error:

(node:10220) UnhandledPromiseRejectionWarning: TypeError: Error processing argument at index 1, conversion failure from
    at Object.showErrorBox (node:electron/js2c/browser_init:2:21451)
    at initializeApp (C:\teleport\web\packages\teleterm\build\app\main\index.js:38975:21)

This led to electron/electron#6793 which made me realize that I was passing error as the second argument to dialog.showErrorBox and it was throwing an error because of that. The second argument is supposed to be a string, but since error in this context has type any, TS wasn't complaining about this.

It looks like the ensureError utility is actually paying off!

Comment on lines +103 to +110
const errorToReport = error.message.includes(process.spawnfile)
? error
: // Attach spawnfile so that the process can be identified without looking at the stacktrace.
// resolveNetworkAddress is the only function that ends up surfacing to the UI the error
// event of a process.
new Error(`${process.spawnfile}: ${error.message}`, {
cause: error,
});
Copy link
Copy Markdown
Member Author

@ravicious ravicious Jul 22, 2025

Choose a reason for hiding this comment

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

I'm not sure if this is necessary, but I don't think there's any guarantee that the error message of an error from the error event of a process is going to include the spawnfile.

It certainly happens for EACCESS on macOS as shown in the screenshot, hence why I'm prepending the spawnfile only if the message doesn't already include it. Since it's an async error, the stacktrace itself is not going to include any relevant part like MainProcess.initTshd, that's why I'm trying to ensure that the error message itself identifies the process.

macos-spawn-error

this.initIpc();
} catch (err) {
this.logger.error('Failed to start main process: ', err.message);
app.exit(1);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Error handling was moved out of here to main.ts so that we can properly stop execution code after catching an error, as app.exit(1) does not stop execution immediately (see RCA for more info).

Comment thread web/packages/teleterm/src/mainProcess/mainProcess.ts
@ravicious ravicious requested a review from gzdunek July 22, 2025 12:59
@ravicious ravicious added this pull request to the merge queue Jul 23, 2025
Merged via the queue into master with commit 1f4e8e1 Jul 23, 2025
57 checks passed
@ravicious ravicious deleted the r7s/main-process-err branch July 23, 2025 11:33
@backport-bot-workflows
Copy link
Copy Markdown
Contributor

@ravicious See the table below for backport results.

Branch Result
branch/v16 Create PR
branch/v17 Create PR
branch/v18 Create PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport/branch/v17 backport/branch/v18 no-changelog Indicates that a PR does not require a changelog entry size/sm ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor error handling to stop execution after an error from MainProcess.create

3 participants