Skip to content

Fix Flash engine startup on Windows#145

Open
wangyuxun6699 wants to merge 2 commits into
MigoXLab:mainfrom
wangyuxun6699:changes
Open

Fix Flash engine startup on Windows#145
wangyuxun6699 wants to merge 2 commits into
MigoXLab:mainfrom
wangyuxun6699:changes

Conversation

@wangyuxun6699

Copy link
Copy Markdown

This PR fixes Windows compatibility issues when running Flash Gen Mode.

  • Make Flash runner signal handling platform-aware:
    • Register SIGTERM normally.
    • Register SIGHUP only when the current platform supports it.
    • Restore only the signal handlers that were actually installed.
  • Resolve MCP server commands to the executable path returned by shutil.which() before spawning:
    • Fixes Windows .CMD shim startup for tools like chrome-devtools-mcp.
    • Avoids [WinError 2] 系统找不到指定的文件 when subprocess.Popen(..., shell=False) receives a bare command name.

Motivation

On Windows, signal.SIGHUP does not exist, causing Flash Gen Mode to abort during startup.

After that was fixed, the browser MCP process could still fail to spawn because chrome-devtools-mcp is installed as a Windows command shim, such as:

C:\Users\wangy\AppData\Roaming\npm\chrome-devtools-mcp.CMD

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors command resolution in the MCP client and improves signal handling in the runner to dynamically support signals like SIGHUP. The review feedback highlights three key areas for improvement: resolving absolute paths without extensions on Windows by running shutil.which on all commands, removing a duplicate and unreachable except ValueError: block in the signal installation logic, and wrapping signal restoration in a try...except ValueError block to prevent crashes when running in non-main threads.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +155 to +159
resolved_command = self._command
if not os.path.isabs(self._command):
resolved_command = shutil.which(self._command) or ''
if not resolved_command:
raise MCPError(f'{self.name}: command not found on PATH: {self._command!r}')

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.

high

On Windows, absolute paths to command shims (like .CMD or .BAT files) might be specified without their extension. Since shutil.which is only called for non-absolute paths, absolute paths without extensions will not be resolved, leading to FileNotFoundError when spawning the subprocess with shell=False.

We can resolve this by running shutil.which on all commands, falling back to the original command only if it is an absolute path and shutil.which returns None.

Suggested change
resolved_command = self._command
if not os.path.isabs(self._command):
resolved_command = shutil.which(self._command) or ''
if not resolved_command:
raise MCPError(f'{self.name}: command not found on PATH: {self._command!r}')
resolved_command = shutil.which(self._command)
if not resolved_command:
if not os.path.isabs(self._command):
raise MCPError(f'{self.name}: command not found on PATH: {self._command!r}')
resolved_command = self._command

Comment thread webqa_agent/executor/flash/runner.py Outdated
Comment on lines 591 to 599
except ValueError:
for signum, previous_handler in _previous_signal_handlers.items():
try:
signal.signal(signum, previous_handler)
except ValueError:
pass
_previous_signal_handlers.clear()
except ValueError:
pass

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.

medium

There is a duplicate except ValueError: block at the end of the signal installation try block. The second except ValueError: block is redundant and unreachable because any ValueError is already caught and handled by the first except ValueError: block.

Suggested change
except ValueError:
for signum, previous_handler in _previous_signal_handlers.items():
try:
signal.signal(signum, previous_handler)
except ValueError:
pass
_previous_signal_handlers.clear()
except ValueError:
pass
except ValueError:
for signum, previous_handler in _previous_signal_handlers.items():
try:
signal.signal(signum, previous_handler)
except ValueError:
pass
_previous_signal_handlers.clear()

Comment thread webqa_agent/executor/flash/runner.py Outdated
Comment on lines +929 to +930
for signum, previous_handler in _previous_signal_handlers.items():
signal.signal(signum, previous_handler)

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.

medium

Restoring signal handlers in the finally block should be wrapped in a try...except ValueError block. If run_cc_mini is executed in a non-main thread, calling signal.signal will raise a ValueError ('signal only works in main thread'), which could crash the finally block and mask other exceptions or prevent proper cleanup.

        for signum, previous_handler in _previous_signal_handlers.items():
            try:
                signal.signal(signum, previous_handler)
            except ValueError:
                pass

_previous_signal_handlers: dict[int, Any] = {}
signals_to_install = [signal.SIGTERM]

sighup = getattr(signal, "SIGHUP", None)

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: add unit tests for this platform-aware signal handling. You can use monkeypatch.delattr(signal, 'SIGHUP', raising=False) to simulate Windows and assert only SIGTERM is installed/restored (and no AttributeError is raised), plus a POSIX case asserting both SIGTERM and SIGHUP are installed and then restored on unwind.

# Popen's generic FileNotFoundError.
if shutil.which(self._command) is None and not os.path.isabs(self._command):
raise MCPError(f'{self.name}: command not found on PATH: {self._command!r}')
resolved_command = shutil.which(self._command)

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: add unit tests for command resolution. Mock shutil.which and subprocess.Popen and assert Popen receives the resolved full path (covering the Windows .CMD shim case), the not-found + non-absolute case raises MCPError, and the absolute-path fallback passes the original command through unchanged.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, good point. I’ll add unit tests covering both the Windows-like case where signal.SIGHUP is unavailable and the POSIX case where both SIGTERM and SIGHUP are installed and restored.

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