go-delve/delve · error
could not follow task across exec: %d
Error message
could not follow task across exec: %d
What it means
When the inferior calls execve on macOS its task_t changes. trapWait detects this (notification for a different task while the old one is invalid) and re-acquires the exception/notification ports with reset_exception_ports. If that mach call fails, Delve cannot follow the debuggee across the exec and returns "could not follow task across exec: <kret>" with the mach kernel return code.
Source
Thrown at pkg/proc/native/proc_darwin.go:355
// Call trapWait again, it seems
// MACH_RCV_INTERRUPTED is emitted before
// process natural death _sometimes_.
continue
}
return nil, nil
case 0:
return nil, fmt.Errorf("error while waiting for task")
}
// In macOS 10.12.1 if we received a notification for a task other than
// the inferior's task and the inferior's task is no longer valid, this
// means inferior called execve and its task_t changed.
if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 {
dbp.os.task = task
kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not follow task across exec: %d\n", kret)
}
}
// Since we cannot be notified of new threads on OS X
// this is as good a time as any to check for them.
dbp.updateThreadList()
th, ok := dbp.threads[int(port)]
if !ok {
halt := dbp.os.halt
if halt {
dbp.os.halt = false
return th, nil
}
if dbp.firstStart || th.singleStepping {
dbp.firstStart = false
return th, nil
}
if err := th.resume(); err != nil {View on GitHub (pinned to a23773e6c3)
Solutions
- Decode the printed kern_return_t to identify the mach failure (e.g. invalid task/port).
- Avoid exec-ing through wrappers; launch the final binary directly with `dlv exec`.
- Set breakpoints and use `follow-exec` compatible workflows: continue after the exec instead of stopping inside the wrapper.
- Check the new binary isn't SIP-protected or setuid.
- Update Delve; exec-following behavior is version-sensitive on macOS.
Example fix
// before dlv exec ./run.sh // script execs ./app // after go build -o ./app . dlv exec ./app
Defensive patterns
Strategy: validation
Validate before calling
// Prefer launching the final binary directly over one that will exec:
fi, err := os.Stat(target)
if err != nil {
return err
}
if hasShebangWrapper(fi) { // e.g. script that execs another binary
return fmt.Errorf("debug the exec'd binary %q directly instead", actualBinary)
} Try / catch
_, err := dbp.trapWait(-1)
if err != nil && strings.Contains(err.Error(), "could not follow task across exec") {
// log the kern_return_t; relaunch pointing at the post-exec binary
} Prevention
- Debug the final binary directly rather than shell wrappers that exec.
- Avoid setuid/SIP-protected targets when following exec is needed.
- Note the kern_return_t in the message to diagnose the mach failure.
- Test exec flows on the exact macOS version in use; behavior varies.
When it happens
Trigger: Debugging a program that calls execve (or a shell script wrapper) on macOS, and reset_exception_ports on the new task returns non-KERN_SUCCESS (invalid task, port rights problem).
Common situations: `dlv exec /bin/sh script.sh` where the script execs another binary; Go programs re-executing themselves; setuid or hardened-runtime targets refusing port manipulation.
Related errors
- could not resume task
- could not attach to %d
- error while waiting for task
- error waiting for thread stop %d
- could not set pc
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/0e52d30360b9adf4.
Report an issue: GitHub.