# How no-mistakes Handles Windows Console Interactions with Process Hardening

> Learn how no-mistakes prevents Windows console flooding using process hardening. Discover how to use winproc Harden to hide subprocess windows while preserving I/O redirection for a cleaner experience.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: deep-dive
- Published: 2026-07-12

---

**The no-mistakes repository prevents console window flooding on Windows by using `winproc.Harden`, a helper that sets `CREATE_NO_WINDOW` and `HideWindow` flags on subprocesses while preserving I/O pipe redirection.**

When running as a console-less daemon on Windows, no-mistakes spawns numerous child processes including Git operations, shell commands, and agent processes. Without intervention, each subprocess would trigger the Windows loader to allocate a visible console window, flooding the screen with dozens of pop-ups. The project solves this through a centralized process hardening utility implemented in `internal/winproc`.

## The Problem: Console Window Flooding

Windows behavior dictates that child processes launched from a console-less parent automatically receive a fresh visible console window for their lifetime. Since no-mistakes operates as a daemon and runs agents, Git commands, provider CLIs, and various helper subprocesses, this would result in a cascade of rapidly appearing and disappearing console windows.

Issue #287 in the repository specifically addresses this UX problem. The solution requires suppressing the console window while maintaining the ability to capture stdout and stderr through pipes.

## The Solution: The `winproc.Harden` Utility

The project introduces a small Windows-specific package that hardens child processes to prevent console allocation. This approach uses platform-specific system calls while providing a unified API across all operating systems.

### Windows-Specific Implementation

In [`internal/winproc/harden_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/winproc/harden_windows.go), the `Harden` function modifies `exec.Cmd` configurations to prevent window creation:

```go
//go:build windows

package winproc

import (
	"os/exec"
	"syscall"

	"golang.org/x/sys/windows"
)

// Harden marks cmd so Windows launches it without a visible console window,
// preserving any creation flags already set (e.g. CREATE_NEW_PROCESS_GROUP)
// and leaving stdout/stderr redirection untouched.
func Harden(cmd *exec.Cmd) {
	if cmd == nil {
		return
	}
	if cmd.SysProcAttr == nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{}
	}
	cmd.SysProcAttr.CreationFlags |= windows.CREATE_NO_WINDOW
	cmd.SysProcAttr.HideWindow = true
}

```

The function sets two critical attributes:
- **`windows.CREATE_NO_WINDOW`** – Tells the Windows loader to skip console allocation entirely
- **`HideWindow = true`** – Ensures the window remains hidden if created

The implementation preserves existing `SysProcAttr` settings, making it safe to call even when other process attributes (like `CREATE_NEW_PROCESS_GROUP`) are already configured.

### Cross-Platform Compatibility

For non-Windows builds, [`internal/winproc/harden_other.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/winproc/harden_other.go) provides a no-op stub:

```go
//go:build !windows

package winproc

import "os/exec"

func Harden(cmd *exec.Cmd) {
	// No-op on non-Windows platforms
}

```

This ensures the same API works across Linux, macOS, and Windows without conditional compilation at every call site.

## Where Process Hardening Is Applied

The `winproc.Harden` call appears throughout the codebase wherever subprocesses might spawn on Windows:

- **[`internal/git/git.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/git.go)** – Hardens all Git invocations (lines 43, 76, 127, 169, 191, 206, 317, 526)
- **[`internal/shellenv/shell_command_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/shellenv/shell_command_windows.go)** – Applies to generic shell commands (lines 62, 90, 151)
- **[`internal/daemon/proc_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/proc_windows.go)** – Hardens daemon-side subprocesses (line 31)
- **[`internal/daemon/recover_servers_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/recover_servers_windows.go)** – Protects recovery helpers (line 16)
- **[`internal/daemon/service_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/service_render.go)** – Secures service-render commands (line 252)
- **[`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go)** – Hardens configuration-related commands (line 420)
- **[`internal/cli/doctor.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/doctor.go)** – Protects health-check Git commands (line 44)
- **[`internal/agent/server_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/server_windows.go)** – Hardens the managed agent server (line 14)
- **[`internal/pipeline/steps/common_exec.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/common_exec.go)** – Secures pipeline step executors (line 161)
- **[`internal/scm/scm.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/scm.go)** – Hardens generic SCM commands (line 197)

Because the function is **idempotent**, developers can call it unconditionally without checking whether `SysProcAttr` is already populated.

## Technical Implementation Examples

### Direct Usage Pattern

When executing external commands, hardening is applied immediately after command construction:

```go
package main

import (
	"os"
	"os/exec"

	"github.com/kunchenguid/no-mistakes/internal/winproc"
)

func runGitStatus() error {
	cmd := exec.Command("git", "status")
	
	// Prevent console window on Windows
	winproc.Harden(cmd)
	
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

```

### Pipeline Integration

In [`internal/pipeline/steps/common_exec.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/common_exec.go), hardening integrates with the execution context:

```go
func execCommand(ctx context.Context, cmd *exec.Cmd) error {
	// Setup command context and cancellation...
	
	// Ensure no console window appears on Windows
	winproc.Harden(cmd)
	
	// Execute with proper I/O handling
	return shellenv.RunShellCommand(ctx, cmd)
}

```

The hardening occurs before stream attachment, ensuring that while the console window is suppressed, the parent process retains full access to the child's output and error streams through pipes.

## Summary

- **`winproc.Harden`** in [`internal/winproc/harden_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/winproc/harden_windows.go) sets `CREATE_NO_WINDOW` and `HideWindow` flags to prevent visible console allocation on Windows.
- The function is **idempotent** and safe to call multiple times, preserving existing `SysProcAttr` configurations like `CREATE_NEW_PROCESS_GROUP`.
- A **no-op stub** in [`harden_other.go`](https://github.com/kunchenguid/no-mistakes/blob/main/harden_other.go) ensures cross-platform compatibility without build tags at every call site.
- The utility is applied consistently across Git operations, shell commands, daemon processes, and pipeline steps to prevent window flooding while maintaining I/O redirection.
- Standard streams remain functional through pipes, allowing the daemon to capture output normally despite the lack of a console window.

## Frequently Asked Questions

### What is process hardening in no-mistakes?

Process hardening in no-mistakes refers to the `winproc.Harden` utility that configures Windows subprocesses to run without visible console windows. According to the source code in [`internal/winproc/harden_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/winproc/harden_windows.go), it modifies `SysProcAttr` to include `CREATE_NO_WINDOW` and `HideWindow` flags, preventing the Windows loader from allocating a console window while keeping stdout and stderr pipes intact.

### Why does no-mistakes need to suppress console windows on Windows?

When no-mistakes runs as a console-less daemon, spawning child processes like Git, shell commands, or agents would normally cause Windows to open a fresh visible console window for each subprocess. As documented in issue #287, this results in dozens of rapidly appearing and disappearing windows that flood the screen. Process hardening eliminates this behavior while preserving the ability to capture command output.

### Is `winproc.Harden` safe to call multiple times?

Yes, `winproc.Harden` is designed to be idempotent and safe for repeated invocation. The function checks if `cmd.SysProcAttr` is nil and initializes it if necessary, then uses bitwise OR to add the `CREATE_NO_WINDOW` flag. This means calling it multiple times or on commands with existing process attributes will not cause errors or lose existing configuration.

### Does process hardening affect stdout and stderr capture?

No, process hardening does not interfere with I/O redirection. The implementation specifically preserves stdout and stderr pipes, allowing the parent daemon to capture output and errors normally. The `CREATE_NO_WINDOW` flag only suppresses the console window allocation; it does not detach the process from its parent's standard streams or pipes.