Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/daemon-go/internal/detect/process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"encoding/json"
"os/exec"
"time"

"github.com/dbisina/relay/internal/process"
)

type procInfo struct {
Expand All @@ -29,6 +31,7 @@ func listProcesses() ([]procInfo, error) {
// Force array output so a single-process result still parses as JSON array.
const ps = `@(Get-CimInstance Win32_Process | Select-Object ProcessId,Name,CommandLine) | ConvertTo-Json -Compress -Depth 3`
cmd := exec.CommandContext(ctx, "powershell.exe", "-NoProfile", "-NonInteractive", "-Command", ps)
process.HideWindow(cmd)
out, err := cmd.Output()
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions packages/daemon-go/internal/fsm/durability.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"strconv"
"strings"
"time"

"github.com/dbisina/relay/internal/process"
)

const (
Expand Down Expand Up @@ -258,6 +260,7 @@ func (d *DurabilityManager) checkDrift(files []string) error {
func (d *DurabilityManager) git(args ...string) error {
cmd := exec.Command("git", args...)
cmd.Dir = d.workDir
process.HideWindow(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%w: %s", err, bytes.TrimSpace(out))
Expand All @@ -272,6 +275,7 @@ func (d *DurabilityManager) gitOutput(args ...string) (string, error) {
func (d *DurabilityManager) gitOutputRaw(args ...string) (string, error) {
cmd := exec.Command("git", args...)
cmd.Dir = d.workDir
process.HideWindow(cmd)
out, err := cmd.Output()
if err != nil {
return "", err
Expand Down
6 changes: 6 additions & 0 deletions packages/daemon-go/internal/process/job_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ func SetupChildProcess(cmd *exec.Cmd) error {
return nil
}

// HideWindow is a no-op on Unix: only Windows auto-allocates a console
// window for a spawned console-subsystem process with no console to inherit.
func HideWindow(cmd *exec.Cmd) {
_ = cmd
}

// AssignToJobObject is a no-op on Unix.
func AssignToJobObject(cmd *exec.Cmd) error {
return nil
Expand Down
23 changes: 21 additions & 2 deletions packages/daemon-go/internal/process/job_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,36 @@ func initGlobalJob() {
}

// SetupChildProcess configures SysProcAttr so the child starts in a new
// process group (prevents Ctrl-C propagation) and can be assigned to the
// Job Object after Start().
// process group (prevents Ctrl-C propagation), never gets a console window
// of its own, and can be assigned to the Job Object after Start().
//
// HideWindow matters beyond cosmetics: since the desktop app's bundled
// daemon is itself linked with no console (a Windows GUI-subsystem binary,
// so Electron spawning it headlessly can't flash one either), any
// console-subsystem child it starts, the provider CLI this configures,
// would otherwise get a fresh console window auto-allocated by the loader
// because there is none to inherit. CREATE_NO_WINDOW stops that.
func SetupChildProcess(cmd *exec.Cmd) error {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &windows.SysProcAttr{}
}
// New process group: Ctrl-C in the terminal does not kill the agent.
cmd.SysProcAttr.CreationFlags |= windows.CREATE_NEW_PROCESS_GROUP
cmd.SysProcAttr.HideWindow = true
return nil
}

// HideWindow prevents a background one-shot command (a git snapshot, a
// detection-scan probe, a verification step) from getting a console window,
// for callers that only need that and not the rest of SetupChildProcess's
// process-group / Job Object machinery meant for a long-lived agent.
func HideWindow(cmd *exec.Cmd) {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &windows.SysProcAttr{}
}
cmd.SysProcAttr.HideWindow = true
}

// AssignToJobObject must be called immediately after cmd.Start().
// It attaches the new process to the global kill-on-close Job Object.
func AssignToJobObject(cmd *exec.Cmd) error {
Expand Down
3 changes: 3 additions & 0 deletions packages/daemon-go/internal/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"runtime"
"strings"
"time"

"github.com/dbisina/relay/internal/process"
)

// Check is the result of running one acceptance command.
Expand Down Expand Up @@ -78,6 +80,7 @@ func Run(ctx context.Context, workDir string, cmds []string) Result {
c = exec.CommandContext(cctx, "sh", "-c", cmd)
}
c.Dir = workDir
process.HideWindow(c)
var buf bytes.Buffer
c.Stdout = &buf
c.Stderr = &buf
Expand Down
4 changes: 4 additions & 0 deletions packages/daemon-go/internal/worktree/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"os/exec"
"path/filepath"
"strings"

"github.com/dbisina/relay/internal/process"
)

// Manager — owns one worktree per session.
Expand Down Expand Up @@ -176,6 +178,7 @@ func (m *Manager) ApplyDirtySnapshot(worktreePath string, snap *DirtySnapshot) e
cmd := exec.Command("git", "-c", "core.autocrlf=false", "apply", "--whitespace=nowarn", "-")
cmd.Dir = worktreePath
cmd.Stdin = strings.NewReader(snap.Patch)
process.HideWindow(cmd)
var out bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &out
if err := cmd.Run(); err != nil {
Expand All @@ -198,6 +201,7 @@ func (m *Manager) ApplyDirtySnapshot(worktreePath string, snap *DirtySnapshot) e
func (m *Manager) git(args ...string) (string, error) {
cmd := exec.Command("git", args...)
cmd.Dir = m.RepoDir
process.HideWindow(cmd)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
Expand Down
Loading