• Kill a local process from the TUI, and stop the agent reporting dead ones (#40)
    Build Debian Packages / Build .deb for x86_64-unknown-linux-gnu (push) Has been cancelled
    Build Debian Packages / Build .deb for aarch64-unknown-linux-gnu (push) Has been cancelled
    Build Debian Packages / Build .deb for armv7-unknown-linux-gnueabihf (push) Has been cancelled
    Build Debian Packages / Build .deb for riscv64gc-unknown-linux-gnu (push) Has been cancelled
    CI / build (ubuntu-latest) (push) Has been cancelled
    CI / build (windows-latest) (push) Has been cancelled
    Build Debian Packages / Combine all .deb packages (push) Has been cancelled
    Build Debian Packages / Publish to APT Repository (push) Has been cancelled
    Build Debian Packages / Create GitHub Release (push) Has been cancelled

    jason released this 2026-08-23 15:11:17 +00:00 | 11 commits to master since this release

    • fix(agent): drop processes that no longer exist

    refresh_processes_specifics was called with remove_dead_processes = false
    against a long-lived System, so the agent accumulated every process it had
    ever seen and went on reporting them. Measured on a Pi 5 after a few hours of
    build churn: 21,648 processes reported, 289 actually running, growing a few
    every poll.

    Three consequences, in ascending order of how confusing they are:

    • unbounded memory growth, and every poll iterating ~75x more entries than
      it should
    • process_count — the client's "Top Processes (N total)" — is meaningless
    • a process you kill keeps its row forever, because the agent keeps sending
      it. Killing it again reports "no longer exists", since the kernel is
      telling the truth and the agent is not.

    The third is how this was found: no amount of client-side reconciliation could
    fix a list whose producer never forgets anything.

    Passing true is only correct because these two sites use
    ProcessesToUpdate::All. With Some(pids) sysinfo treats every process outside
    the list as dead and removes it, so the per-PID refresh in
    collect_process_metrics must keep false — noted in a comment there.

    Verified by driving the TUI against a rebuilt agent: reported count 292 vs 290
    real, and a killed row is removed once and never reappears.

    Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

    • feat(socktop): kill a local process from the TUI

    btop-style process termination, for local agents only. The signal is sent by
    socktop itself through a direct sysinfo call — nothing is transmitted to the
    agent, and the agent and connector have no kill capability at all.

    Why local-only: the PIDs on screen are reported by the agent, and the signal is
    sent with socktop's own OS privileges. A PID is therefore only meaningful, and
    only safe to act on, when the agent lives on this machine; acting on a remote
    agent's PIDs would signal whatever unrelated local process happened to share
    that number. local::agent_is_local treats an address as local when it is
    loopback or when an ephemeral bind succeeds (which only works for an address on
    one of our own interfaces, so it also covers reaching our own agent by LAN IP),
    requires every address a hostname resolves to to be local, and fails closed.

    • t on the selected process, and t inside Process Details. One key for
      both: k scrolls the thread table in the modal, so it could not be reused
      there.
    • The confirmation offers Terminate (focused first, so a reflexive Enter is
      the safe one), Force kill, and Cancel. Keeping SIGKILL behind a second
      button rather than a second keybinding means the destructive option has to
      be chosen deliberately.
    • The selection hint gained the key, but only for a local agent — advertising
      a key that deliberately does nothing is worse than no hint. Same for the
      details modal's help line.

    The list is reconciled after a signal rather than left to the next poll. A
    signalled PID goes on a watch list re-checked each metrics tick, because SIGTERM
    is a request: the process is usually still alive at signal time, and its row
    should go when it actually exits (or stay, if it ignores the signal). PIDs
    confirmed gone are remembered briefly, since the agent serves Processes from a
    1500ms cache and would otherwise hand back a pre-kill snapshot. A selection
    whose process has left the list is dropped, and the details view closes for a
    process that no longer exists — including when it dies on its own, which
    previously flipped that modal to "Agent Update Required" because the wire cannot
    distinguish "no such PID" from "endpoint unsupported".

    Also fixes two pre-existing UI faults found on the way:

    • The selection hint was sized from its full label including the process name
      and skipped entirely when that exceeded the pane width — so it vanished
      exactly when a long-named process was selected. The name is now the elastic
      part, and widths are measured in columns rather than bytes.
    • Confirmation and Info dialogs laid their content out over the whole modal
      rect instead of the block's inner rect, putting the first line of text on
      the border row, and fell into the catch-all 70%x50% sizing arm, so a
      one-line question got half the screen. They now size to their content and
      use the theme's colors like the connection-error modal does.

    Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

    • fix(kill): close stacked details view, guard PID reuse, scale settle with interval

    Review fixes for the process-kill feature:

    1. Killing from INSIDE the details view left it open forever, frozen on
      the dead process (reproduced live): the 'Signal sent' Info modal sits
      on top when the death is confirmed on the next tick, the old top-only
      close_process_details missed it, gone-PIDs are processed once, and the
      details-poll fallback was gated on the selection the kill had just
      cleared. The close now removes the dead PID's view wherever it sits in
      the stack — which also retires a dead parent's view from under a child
      in a navigation chain, so backing out lands on the process list rather
      than a frozen corpse view. Tests updated to the new semantics, plus a
      regression test for the Info-stacked case.

    2. PID-reuse guard: the PID comes from an agent snapshot and the
      confirmation can sit open indefinitely, so by signal time the kernel
      may have recycled the number. kill_local_process now takes the name
      the user confirmed and refuses to signal a PID whose current owner
      does not match ('PID N now belongs to X, not Y').

    3. A transient request error no longer closes the details view: with
      process_details_answered set, any Err was read as 'process gone',
      including socket blips. The view now closes only when the PID is also
      absent from the agent's own process list.

    4. PROC_CACHE_SETTLE scales with the user's processes interval (floor at
      the old 1.6s default-TTL value), and the tombstone lifetime rides on
      top of it — users who raise the agent's Processes TTL raise the client
      interval to match, so the interval is the best client-side signal for
      how stale an agent snapshot can be.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • fix(kill): resume polling for the details view that resurfaces from a chain

    Reported: kill still orphaned a window via Enter (child details) -> P
    (parent details) -> t (terminate parent). The parent's view closed
    correctly, but the child's view underneath resurfaced with no selection
    (forget_process_row had cleared it — it pointed at the parent) and wiped
    data; the selection-gated details poll never refilled it.

    close_details_for_gone_process now retargets the selection to the
    uppermost remaining ProcessDetails view (looking through stacked
    Info/Confirmation modals) and makes the poll due immediately — the same
    retarget SwitchToParentProcess performs on the way down the chain.

    Same class of hole in plain navigation, fixed alongside: Esc-ing back
    from a parent view left the selection on the PARENT, so the resurfaced
    child-titled view refilled with the parent's data. The dismiss arm now
    retargets the selection to whatever details view it lands on.

    Both verified live: child -> P -> kill parent -> child view resurfaces
    populated and updating; child -> P -> Esc -> child view shows the child.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com

    • docs: changelog section for the process-kill feature and agent dead-process fix

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com


    Co-authored-by: Claude Opus 5 (1M context) noreply@anthropic.com

    Downloads