Skip to content

[Windows] Executable name duplicated in arguments when spawning process via Pty.start #19

@kwrkb

Description

@kwrkb

Flutter PTY Windows Bug Report & Fix

Title

[Windows] Executable name duplicated in arguments when spawning process via Pty.start

Description

On Windows, when Pty.start creates a process, the executable name is incorrectly duplicated in the command line arguments passed to the child process.

The Dart side (Pty.start) correctly constructs the argument list starting with the executable path at index 0 (standard convention). However, the Windows native implementation (flutter_pty_win.c) treats the entire argument list as additional arguments to be appended to the command line, failing to skip argv[0].

This causes critical issues with shells like PowerShell 7 (pwsh), which interpret the first positional argument as a script file path if it's not a flag, leading to startup failure.

Steps to Reproduce

  1. Run a Dart/Flutter app using flutter_pty on Windows.
  2. Attempt to start a process:
    Pty.start('pwsh.exe', arguments: ['-NoLogo']);
  3. Expected Behavior:
    • Command line: pwsh.exe -NoLogo
    • Process starts successfully.
  4. Actual Behavior:
    • Command line becomes: pwsh.exe pwsh.exe -NoLogo
    • pwsh error: The argument 'pwsh.exe' is not recognized as the name of a script file.

Environment

  • OS: Windows 11
  • Package: flutter_pty (v0.4.2)

Technical Fix Instructions

To resolve this, the native Windows implementation must skip the first element of the argument array (argv[0]) when constructing the command line string, as argv[0] is the executable itself which is already handled.

Target File

src/flutter_pty_win.c

Function

static LPWSTR build_command(char *executable, char **arguments)

Changes

Change the loop initialization from 0 to 1 in two places:

  1. When calculating the total string length.
  2. When building the command string.

1. Length Calculation Loop

    if (arguments != NULL)
    {
        // CHANGE: Start from 1 to skip executable name
        int i = 1;

        while (arguments[i] != NULL)
        {
            command_length += (int)strlen(arguments[i]) + 1;
            i++;
        }
    }

2. String Construction Loop

        if (arguments != NULL)
        {
            // CHANGE: Start from 1 to skip executable name
            int j = 1;

            while (arguments[j] != NULL)
            {
                command[i++] = ' ';

                int k = 0;

                while (arguments[j][k] != 0)
                {
                    command[i] = (WCHAR)arguments[j][k];
                    i++;
                    k++;
                }

                j++;
            }
        }

This change prevents the executable name from appearing twice in the final command line string passed to CreateProcessW.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions