Add Unix domain socket control interface for TUI (#482)#488
Add Unix domain socket control interface for TUI (#482)#488
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
Expose a newline-delimited JSON protocol over a Unix socket that
allows external tools to programmatically query state and trigger
mutations in a running TUI instance. Supports 12 commands: 4 queries
(get-state, get-filter, get-jobs, get-selected) and 8 mutations
(set-filter, clear-filter, set-hide-closed, select-job, set-view,
close-review, cancel-job, rerun-job).
Socket created at {DataDir}/tui.{PID}.sock on every TUI launch with
0600 permissions. Runtime metadata written to tui.{PID}.json for
discoverability. Stale sockets from dead processes cleaned on startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ata gating
- Lstat the socket path before removal: only remove verified-stale
Unix sockets (dial fails), refuse to delete regular files or sockets
with a live listener
- Create parent directories with MkdirAll before net.Listen so fresh
installs and custom --control-socket paths in missing dirs work
- Only write tui.{PID}.json runtime metadata after the listener starts
successfully, preventing external tools from discovering a broken
endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously removeStaleSocket treated any dial failure as proof that a socket was stale. Non-ECONNREFUSED errors (e.g. DGRAM sockets, permission denied) are ambiguous and could indicate a live non-stream socket. Now only ECONNREFUSED triggers removal; other dial errors return an error to the caller. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The manual unwrapping of net.OpError → os.SyscallError was fragile and would miss ECONNREFUSED if Go or the OS wrapped the errno differently. errors.Is walks the full chain regardless of nesting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- select-job: reject jobs hidden by filters or hide-closed, not just jobs missing from m.jobs - close-review / cancel-job: only reflow selection when the mutated job is the currently selected row, preventing unexpected cursor movement when operating on a different job - Runtime metadata: populate RepoFilter/BranchFilter from the model's resolved active filters instead of raw CLI config, so auto-filter-by-repo/branch is advertised correctly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- isProcessAlive: use platform-specific build files so Windows uses tasklist instead of signal 0, and Unix handles EPERM (process exists but owned by another user) - Socket permissions: set umask(0177) before net.Listen so the socket is created with 0600 from the start, closing the TOCTOU window between Listen and Chmod - Accept loop: add 100ms backoff on transient errors to prevent tight CPU-pegging loops on EMFILE or similar - Runtime metadata: add regression test verifying that auto-filter repo/branch settings are reflected in the written metadata Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- isProcessAlive: use errors.Is(err, syscall.EPERM) instead of direct equality to handle wrapped permission errors - Extract buildTUIRuntimeInfo helper used by both Run() and the regression test, so the test exercises the actual metadata construction path rather than manually copying model fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Close the pipe write end before Kill so bubbletea's readLoop sees EOF and exits before shutdown closes the cancel reader. Wait for Run to complete before returning from cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Deep-copy pointer fields (Closed, Verdict) and slices (RepoFilter, FilterStack) in control response builders so json.Marshal in connection goroutines doesn't race with mutations in the Bubble Tea update loop - CleanupStaleTUIRuntimes now uses removeStaleSocket to verify socket type and liveness before removal, preventing deletion of non-socket files or live sockets at shared custom paths - Move SOCK_DGRAM and POSIX permission tests to control_unix_test.go with !windows build tag since these features don't exist on Windows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite all assertions in control_test.go and control_unix_test.go to use testify assert/require instead of raw if/t.Errorf/t.Fatalf patterns, matching the project-wide migration in #487. Update CLAUDE.md and AGENTS.md to document that testify is the standard assertion library for all Go tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Summary
get-state,get-filter,get-jobs,get-selected) and 8 mutations (set-filter,clear-filter,set-hide-closed,select-job,set-view,close-review,cancel-job,rerun-job){DataDir}/tui.{PID}.sockon every TUI launch with 0600 permissions (umask-based, no TOCTOU window); runtime metadata written totui.{PID}.jsonfor discoverabilityselect-jobrejects jobs hidden by active filters;close-review/cancel-jobonly reflow selection when the mutated job is the currently selected row--control-socketflag for custom socket paths🤖 Generated with Claude Code