go-delve/delve · error

%s

Error message

%s

What it means

TargetGroup.Detach aggregates per-process detach failures and returns them joined as a single multi-line error via fmt.Errorf("%s", ...). The debugger failed to detach one or more live target processes (or their procgrp.Close() cleanup was skipped because errors occurred). Each line reads 'could not detach process <pid>: <reason>'.

Source

Thrown at pkg/proc/target_group.go:246

	return r
}

// Detach detaches all targets in the group.
func (grp *TargetGroup) Detach(kill bool) error {
	var errs []string
	for i := len(grp.targets) - 1; i >= 0; i-- {
		t := grp.targets[i]
		isvalid, _ := t.Valid()
		if !isvalid {
			continue
		}
		err := grp.detachTarget(t, kill)
		if err != nil {
			errs = append(errs, fmt.Sprintf("could not detach process %d: %v", t.Pid(), err))
		}
	}
	if len(errs) > 0 {
		return fmt.Errorf("%s", strings.Join(errs, "\n"))
	}
	return grp.procgrp.Close()
}

// detachTarget will detach the target from the underlying process.
// This means the debugger will no longer receive events from the process
// we were previously debugging.
// If kill is true then the process will be killed when we detach.
func (grp *TargetGroup) detachTarget(t *Target, kill bool) error {
	if !kill {
		if t.asyncPreemptChanged {
			setAsyncPreemptOff(t, t.asyncPreemptOff)
		}
		for _, bp := range t.Breakpoints().M {
			if bp != nil {
				err := t.ClearBreakpoint(bp.Addr)
				if err != nil {
					return err

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Inspect the per-pid lines in the error to see which process failed and why
  2. Verify the target process still exists and is traceable (check /proc/<pid>/status, TracerPid)
  3. Clear all breakpoints/restore state before calling Detach, or retry Detach shortly after resuming
  4. If the process is a zombie or already dead, it can be removed from the group or ignored
  5. As a last resort use Detach(true) to kill the process instead of resuming it

Example fix

// before
if err := dlvClient.Detach(false); err != nil { return err } // fails: process already exited
// after
if st, _ := client.State(); st.Exited {
    // process gone: nothing to detach gracefully, close connection instead
    return client.Detach(true) // or handle exit before detaching
}
return client.Detach(false)
Defensive patterns

Strategy: try-catch

Validate before calling

ok, err := grp.Valid()
if !ok {
    // no live target to detach; skip or handle err (usually process exited)
    return nil
}

Type guard

func isDetachErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "could not detach process")
}

Try / catch

if err := grp.Detach(false); err != nil {
    for _, line := range strings.Split(err.Error(), "\n") {
        var pid int
        if n, _ := fmt.Sscanf(line, "could not detach process %d", &pid); n == 1 {
            log.Printf("detach failed for pid %d: %s", pid, line)
        }
    }
}

Prevention

When it happens

Trigger: Calling Detach(kill) (via debugger.Detach, used by TestUnsupportedArch, TestAttachDetach, TestWaitForAttach) when detachTarget fails: clearing a breakpoint fails because process memory is unreadable, restoring async-preempt state fails, or procgrp.Detach fails (process already exited, ptrace PTRACE_DETACH denied, thread in uninterruptible state).

Common situations: Target process crashed or exited before detach; tracing permissions lost (ptrace scope); kernel refuses PTRACE_DETACH while a thread is stopped at a signal; breakpoint clearing hits unmapped memory after exec changes; zombie processes in the group.

Related errors


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