go-delve/delve · critical

could not fork/exec

Error message

could not fork/exec

What it means

Delve's Darwin native backend launches the debuggee via a C fork/exec helper (fork_exec) that returns the child pid. If the returned pid is <= 0 the helper failed to create or exec the child process, and Launch wraps this condition in the generic message "could not fork/exec". It means the inferior never started, so no debugging session exists.

Source

Thrown at pkg/proc/native/proc_darwin.go:88

	dbp := newProcess(0)
	defer func() {
		if err != nil && dbp.pid != 0 {
			_ = detachWithoutGroup(dbp, true)
		}
	}()
	var pid int
	dbp.execPtraceFunc(func() {
		wd := C.CString(wd)
		defer C.free(unsafe.Pointer(wd))
		ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)),
			wd,
			&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
			&dbp.os.notificationPort)
		pid = int(ret)
	})
	if pid <= 0 {
		return nil, fmt.Errorf("could not fork/exec")
	}
	dbp.pid = pid
	dbp.childProcess = true
	for i := range argvSlice {
		C.free(unsafe.Pointer(argvSlice[i]))
	}

	// Initialize enough of the Process state so that we can use resume and
	// trapWait to wait until the child process calls execve.

	for {
		task := C.get_task_for_pid(C.int(dbp.pid))
		// The task_for_pid call races with the fork call. This can
		// result in the parent task being returned instead of the child.
		if task != dbp.os.task {
			err = dbp.updateThreadListForTask(task)
			if err == nil {
				break

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Verify the target binary path exists and is executable (ls -l, chmod +x) before launching.
  2. Check the argv slice passed to Launch is non-empty and argv[0] is the program path.
  3. Try building with `dlv debug` instead of `dlv exec <bin>` to rule out a stale/broken binary.
  4. Check macOS security settings / SIP and antivirus software that may block ptrace-style helpers.
  5. Rebuild Delve for your macOS version; older C helpers break on newer SDKs.

Example fix

// before
dlv exec ./notbuilt
// after
go build -gcflags="all=-N -l" -o ./app ./cmd/app
dlv exec ./app
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(binPath)
if err != nil || info.IsDir() {
    return fmt.Errorf("binary %s missing: %w", binPath, err)
}
if info.Mode()&0o111 == 0 {
    return fmt.Errorf("binary %s is not executable", binPath)
}

Try / catch

tgt, err := proc.Launch(cmd, wd, false, "", [], "", logger)
if err != nil && strings.Contains(err.Error(), "could not fork/exec") {
    // inspect cmd[0], permissions, and macOS security settings before retrying
}

Prevention

When it happens

Trigger: Calling dlv debug/launch on macOS when the C.fork_exec call in proc_darwin.go fails: bad executable path, missing execute permission, argv/env marshaling problems, or resource limits preventing fork. Detected by `pid <= 0` immediately after the fork/exec call.

Common situations: Debugging a binary path that doesn't exist or isn't executable; macOS security (SIP/hardened runtime) blocking the fork helper; running out of process descriptors; passing a command string with an empty argv slice.

Related errors


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/0ebd8484dbce40b5. Report an issue: GitHub.