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
- Inspect state.ExitStatus / ExitStatus in the halted state to see the program's exit code
- Set breakpoints or use stepping to stop before the program finishes
- Handle the Exited flag in your client loop and stop issuing further RPCs
- 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
- Always check state.Exited before treating state.Err as a real failure
- Stop issuing stepping/continue RPCs once Exited is observed
- Track ExitStatus for exit-code reporting
- Use Restart to begin a new session after exit
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
- could not connect
- detached from the process
- terminated
- stub exited while waiting for connection: %v
- stub exited while attempting to connect: %v
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/9fb01fe54b3d2bdf.
Report an issue: GitHub.