Skip to content

console.log output silently discarded on Windows due to GUI subsystem (PE subsystem = 2) #120

@godcop

Description

@godcop

Compiled exe uses Windows GUI subsystem, causing console.log output to be silently discarded

Perry version: 0.5.112
Platform: Windows x64
Linker: MSVC (Visual Studio 2022 Build Tools)

Description

The default project generated by perry init contains console.log() in the entry file:

// Main entry point

function main(): void {
    console.log("Hello from Perry!");
}

main();

Compiling and running this produces no output in the terminal:

PS> npx perry compile src/main.ts -o myapp.exe
PS> .\myapp.exe
PS>    # <-- no output

perry run also produces no output in the terminal:

PS> npx perry run src/main.ts
Collecting modules...
Found 1 module(s): 1 native, 0 JavaScript
Generating code...
Wrote object file: main_ts.o
Linking (runtime-only)...
Wrote executable: demo
Binary size: 0.5MB

Running demo...

PS>    # <-- still no output

Root Cause

The compiled executable has its PE subsystem set to WINDOWS (2) instead of CONSOLE (3):

Subsystem: 2 (3=CONSOLE, 2=WINDOWS)

On Windows, GUI-subsystem processes do not have their stdout/stderr connected to the terminal, so console.log output is silently discarded. This affects both direct execution and perry run, depending on how the terminal captures subprocess output.

The subsystem value can be verified with:

node -e "const fs=require('fs');const buf=fs.readFileSync('myapp.exe');const peOff=buf.readUInt32LE(0x3C);const sub=buf.readUInt16LE(peOff+0x5C);console.log('Subsystem:',sub,'(3=CONSOLE,2=WINDOWS)')"

Verification

Patching the PE subsystem field to CONSOLE (3) fixes the issue:

node -e "const fs=require('fs');const buf=Buffer.from(fs.readFileSync('myapp.exe'));const peOff=buf.readUInt32LE(0x3C);buf.writeUInt16LE(3,peOff+0x5C);fs.writeFileSync('myapp.exe',buf)"

After patching:

PS> .\myapp.exe
Hello from Perry!

Expected Behavior

One of the following would make sense:

  1. Default to CONSOLE subsystem — Since perry init generates code using console.log, the compiled exe should produce visible output when run from a terminal.
  2. Add a --subsystem compile flag — Allow users to choose between console and windows subsystems (e.g., perry compile src/main.ts -o app.exe --subsystem console).
  3. Auto-detect subsystem — If the source only uses perry/system, perry/i18n, etc. (no perry/ui), default to CONSOLE. If it imports perry/ui, use WINDOWS.
  4. Document the current behavior — At minimum, add a note that console.log output won't appear in the terminal for the default build, and suggest using perry run during development.

Workaround

Patch the PE subsystem after each compile using the following Node.js snippet:

node -e "const fs=require('fs');const buf=Buffer.from(fs.readFileSync('myapp.exe'));const peOff=buf.readUInt32LE(0x3C);buf.writeUInt16LE(3,peOff+0x5C);fs.writeFileSync('myapp.exe',buf)"

Environment

  • Perry: 0.5.112
  • OS: Windows 11 x64
  • Linker: MSVC link.exe (VS 2022 Build Tools, MSVC 14.44.35207)
  • perry doctor warning: runtime library: not found (using prebuilt libperry_runtime.a + libperry_stdlib.a)

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