go-delve/delve · info

Process %d has exited with status %d

Error message

Process %d has exited with status %d

What it means

Client-side state notification in service/rpc2/client.go: when the debuggee terminates, the halted-state callback replaces any pending error with a message reporting the process pid and its exit status, because api.Err cannot be marshalled correctly over JSON-RPC. It signals the debug session has ended, not a debugger malfunction.

Source

Thrown at service/rpc2/client.go:130

func (c *RPCClient) DirectionCongruentContinue() <-chan *api.DebuggerState {
	return c.continueDir(api.DirectionCongruentContinue)
}

func (c *RPCClient) continueDir(cmd string) <-chan *api.DebuggerState {
	ch := make(chan *api.DebuggerState)
	go func() {
		for {
			out := new(CommandOut)
			err := c.callWhileDrainingEvents("Command", &api.DebuggerCommand{Name: cmd, ReturnInfoLoadConfig: c.retValLoadCfg, WithEvents: c.eventsFn != nil}, &out)
			state := out.State
			if err != nil {
				state.Err = err
			}
			if state.Exited {
				// Error types apparently cannot be marshalled by Go correctly. Must reset error here.
				//lint:ignore ST1005 backwards compatibility
				state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus)
			}
			ch <- &state
			if err != nil || state.Exited {
				close(ch)
				return
			}

			isbreakpoint := false
			istracepoint := true
			for i := range state.Threads {
				if state.Threads[i].Breakpoint != nil {
					isbreakpoint = true
					istracepoint = istracepoint && (state.Threads[i].Breakpoint.Tracepoint || state.Threads[i].Breakpoint.TraceReturn)
				}
			}

			if !isbreakpoint || !istracepoint {
				close(ch)

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Inspect state.ExitStatus / ExitStatus in the halted state to see the program's exit code
  2. Set breakpoints or use stepping to stop before the program finishes
  3. Handle the Exited flag in your client loop and stop issuing further RPCs
  4. Re-launch the process (Restart) if more debugging is needed

Example fix

// before
state, err := client.GetStateNonBlocking()
fmt.Println(state.Err)
// after
state, err := client.GetStateNonBlocking()
if state != nil && state.Exited {
    fmt.Printf("process exited with %d\n", state.ExitStatus)
} else {
    fmt.Println(state.Err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// no pre-call validation possible; handle after RPC
state, err := client.GetStateNonBlocking()
willExit := err == nil && state != nil && state.Exited

Type guard

func isProcessExit(state *api.DebuggerState) bool { return state != nil && state.Exited }

Try / catch

state, err := client.GetStateNonBlocking()
if err != nil {
    if state != nil && state.Exited {
        log.Printf("target exited: %d", state.ExitStatus)
        return nil // expected termination, not an RPC failure
    }
    return err
}

Prevention

When it happens

Trigger: Any continue/step/next RPC that observes state.Exited == true after the target process terminated; also delivered on the client's internal state listener channel.

Common situations: Program ran to completion (main returned); target crashed with a non-zero status; process called os.Exit; breakpoint-free run finished.

Related errors


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