rescue

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package rescue provides the out-of-band recovery path for a wedged tmux server. Every normal webui operation is a tmux client call, so a server stuck in a busy-loop (e.g. a re-entrant hook chain) takes the whole UI down with it: each call blocks until timeout. This package never asks the server for state — it discovers the server process via ps, samples stack traces, and kills by PID — so it works exactly when tmux itself does not.

Escalation ladder (mirrors the field-tested SOP): collect evidence first, then kill stuck hook children, and only force-kill the server + clean its socket as the last step.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SocketDir

func SocketDir() string

SocketDir returns the directory tmux keeps its sockets in for this user (TMUX_TMPDIR override respected, /tmp default — same rules as tmux.1).

Types

type Evidence

type Evidence struct {
	Path string `json:"path,omitempty"`
	Text string `json:"text"`
}

Evidence is a diagnose run: the full text plus where it was persisted.

type KillReport

type KillReport struct {
	Killed        []Proc `json:"killed"`
	SocketRemoved bool   `json:"socket_removed,omitempty"`
	After         Status `json:"after"`
}

KillReport describes what a kill action actually did.

type Proc

type Proc struct {
	PID     int     `json:"pid"`
	PPID    int     `json:"ppid"`
	TTY     string  `json:"tty"`
	CPU     float64 `json:"cpu"`
	Etime   string  `json:"etime"`
	Command string  `json:"command"`
}

Proc is one row of the ps snapshot.

type Rescuer

type Rescuer struct {
	// contains filtered or unexported fields
}

Rescuer performs the out-of-band operations. The exec/kill seams are injectable so tests never touch real processes.

func New

func New(tx *tmuxctl.Client) *Rescuer

func (*Rescuer) Diagnose

func (r *Rescuer) Diagnose(ctx context.Context) Evidence

Diagnose collects the evidence bundle: probe status, the tmux slice of ps, and (macOS) a stack sample of each server candidate. Persisted best-effort under the cache dir so it survives the rescue that usually follows.

func (*Rescuer) HandleDiagnose

func (r *Rescuer) HandleDiagnose(w http.ResponseWriter, req *http.Request)

HandleDiagnose — POST /api/rescue/diagnose. Read-only evidence collection; can take a few seconds when stack-sampling a spinning server.

func (*Rescuer) HandleKillHooks

func (r *Rescuer) HandleKillHooks(w http.ResponseWriter, req *http.Request)

HandleKillHooks — POST /api/rescue/kill-hooks. Middle rung of the ladder: only ever signals children of a tmux server process.

func (*Rescuer) HandleKillServer

func (r *Rescuer) HandleKillServer(w http.ResponseWriter, req *http.Request)

HandleKillServer — POST /api/rescue/kill-server. Destructive (all tmux sessions die), so it demands an explicit {"confirm":true} body — a bare POST from a stray fetch or curl must not be enough.

func (*Rescuer) HandleStatus

func (r *Rescuer) HandleStatus(w http.ResponseWriter, req *http.Request)

HandleStatus — GET /api/rescue. Cheap enough to poll: one short-deadline tmux call + one ps.

func (*Rescuer) KillHooks

func (r *Rescuer) KillHooks(ctx context.Context) (KillReport, error)

KillHooks terminates the TTY-LESS direct children of every tmux server candidate — the stuck hook scripts that keep the server's event loop spinning. Pane shells are also direct children of the server but sit on a pty, so hasTTY excludes them (killing those would destroy every session's running programs — verified live: a healthy server showed 14 pane shells as direct children). TERM first, then KILL for survivors.

func (*Rescuer) KillServer

func (r *Rescuer) KillServer(ctx context.Context) (KillReport, error)

KillServer is the last rung: SIGKILL every server candidate (hook children first), wait for them to disappear, then remove the socket dir so the next tmux invocation starts a clean server. Pane shells are deliberately NOT signalled — closing the server tears down their ptys, which is how a manual `pkill -9 tmux` ends them too. Refuses to clean the socket while a candidate is still alive — a live server owning a removed socket is a worse state than the one we started in.

func (*Rescuer) Probe

func (r *Rescuer) Probe(ctx context.Context) Status

Probe classifies tmux health without trusting the server: a short-deadline client call gives responsiveness, ps gives the process picture.

type Status

type Status struct {
	Tmux         string `json:"tmux"` // ok | no_server | wedged | error
	Detail       string `json:"detail,omitempty"`
	Servers      []Proc `json:"servers"`
	HookChildren []Proc `json:"hook_children"`
	SocketDir    string `json:"socket_dir"`
	SocketExists bool   `json:"socket_exists"`
	// InsideTmux warns that this webui process itself lives in a tmux pane:
	// force-killing the server would take the webui down with it.
	InsideTmux bool `json:"inside_tmux"`
}

Status is the out-of-band health picture returned by Probe.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL