Skip to content

Releases: 3leaps/sysprims

v0.1.13

14 Feb 21:39
490fd1b

Choose a tag to compare

v0.1.13 - macOS Command-Line Fidelity Fix & Binding Coverage

Release Date: 2026-02-13
Status: macOS Command-Line Fidelity Fix & Binding Coverage

Summary

This release fixes a high-severity bug where processList() returned truncated cmdline on macOS (just the process name instead of the full argument vector), breaking downstream consumers that filter by command-line arguments. It also exports v0.1.12 process tree capabilities to the FFI layer and Go/TypeScript bindings.

Highlights

  • macOS cmdline fix: cmdline now returns the full argument vector (e.g. ["bun", "run", "scripts/dev.ts", "--root", "/path"]) instead of ["bun"]
  • FFI coverage: descendants and kill-descendants now available through C-ABI FFI
  • Go binding: Descendants() and KillDescendants() with option pattern
  • TypeScript binding: descendants() and killDescendants() via N-API

Changes

Bug Fix: macOS cmdline Truncation

Before (v0.1.12):

{"pid": 12345, "name": "bun", "cmdline": ["bun"]}

After (v0.1.13):

{"pid": 12345, "name": "bun", "cmdline": ["bun", "run", "scripts/dev.ts", "--root", "/some/path"]}

Root cause: The macOS implementation used proc_name() as a placeholder for cmdline, which only returns the process name (16 chars max). The fix uses sysctl(CTL_KERN, KERN_PROCARGS2) — the same kernel API that ps uses — to read the actual argv.

Impact: Any consumer filtering by cmdline arguments on macOS was affected. Known affected: kitfly discoverOrphans() which filters by p.cmdline.some(arg => arg.includes("scripts/dev.ts")).

Implementation details:

  • Two-stage sysctl call: first with null buffer to query required size, then with allocated buffer to read data
  • Parses the KERN_PROCARGS2 buffer format: [argc: i32][exec_path\0][padding \0s][argv[0]\0][argv[1]\0]...
  • Uses String::from_utf8_lossy() for non-UTF-8 safety (matches Linux /proc/[pid]/cmdline pattern)
  • On any error (ESRCH, EPERM, EINVAL, buffer issues), returns empty Vec — best-effort, consistent with the ProcessInfo.cmdline contract: "May be empty if command line cannot be read"

Safety hardening (devrev):

  • PID 0 and overflow-range PIDs (> i32::MAX) rejected before sysctl call
  • argc capped at MAX_ARGC = 4096 to prevent pathological allocation from malformed kernel data
  • Empty argv entries filtered (consistent with Linux /proc/[pid]/cmdline behavior)

FFI: descendants and kill-descendants Exports

v0.1.12 added descendants and kill-descendants to the CLI and Rust crates. This release makes them available through the C-ABI FFI layer:

int32_t sysprims_proc_descendants(const char *config_json, char **result_json_out);
int32_t sysprims_proc_kill_descendants(const char *config_json, char **result_json_out);

Safety enforcement happens in the FFI layer — bindings get PID 1 protection, self-exclusion, and parent protection for free.

Go Binding: Descendants() and KillDescendants()

result, err := sysprims.Descendants(pid, &sysprims.DescendantsOptions{
    MaxLevels: 2,
    Filter: &sysprims.ProcessFilter{Name: "Helper"},
})

killResult, err := sysprims.KillDescendants(pid, &sysprims.KillDescendantsOptions{
    Signal: sysprims.SIGTERM,
    Filter: &sysprims.ProcessFilter{CpuAbove: 80},
    Yes: true,
})

TypeScript Binding: descendants() and killDescendants()

const result = descendants(pid, { maxLevels: 2, filter: { name: "Helper" } });

const killResult = killDescendants(pid, {
    signal: "TERM",
    filter: { cpuAbove: 80 },
    yes: true,
});

Validation

macOS cmdline Fix

Tested on macOS arm64 (Darwin 25.2.0):

# Start a background process with known arguments
$ sleep 999 &
$ cargo run -p sysprims-cli -- pstat --pid $! --json | jq '.processes[0].cmdline'
["sleep", "999"]

# Multi-argument process
$ python3 -c "import time; time.sleep(999)" &
$ cargo run -p sysprims-cli -- pstat --pid $! --json | jq '.processes[0].cmdline'
["python3", "-c", "import time; time.sleep(999)"]

Automated test: test_own_process_has_cmdline — verifies the test runner's own process has non-empty cmdline via snapshot.

Binding Coverage

Function FFI Go TypeScript
descendants() New New New
killDescendants() New New New

Platform Notes

macOS

  • sysctl(CTL_KERN, KERN_PROCARGS2) requires no special privileges for processes owned by the current user
  • For other users' processes, may return empty cmdline (EPERM) — this is expected and consistent with ps behavior
  • The exec path in the KERN_PROCARGS2 buffer is intentionally skipped; only the actual argv entries are returned

Linux

  • No changes — /proc/[pid]/cmdline already worked correctly
  • The new test_own_process_has_cmdline test is gated to #[cfg(target_os = "macos")] since Linux cmdline was never broken

Windows

  • Not in scope for this fix — Windows uses vec![name] placeholder, same as the old macOS behavior
  • Windows cmdline via NtQueryInformationProcess is a separate effort

Safety Considerations

PID Validation (ADR-0011)

The read_cmdline() function enforces:

  • PID 0 rejected (signals caller's process group)
  • PID > i32::MAX rejected (would overflow pid_t cast)
  • argc validation: Capped at 4096 to prevent memory exhaustion from malformed data

Error Handling

All errors in read_cmdline() result in an empty Vec<String> return — no panics, no error propagation. This is consistent with the ProcessInfo.cmdline field contract which documents that cmdline may be empty.

Upgrade Notes

  • No breaking changes — all changes are additive
  • macOS consumers will immediately see full cmdline data where previously truncated
  • Consumers filtering by cmdline may see more matches than before (this is correct behavior)
  • FFI shared library must be rebuilt for all platform targets to include new exports

Files Changed

  • crates/sysprims-proc/src/macos.rs — Added read_cmdline() function using sysctl(CTL_KERN, KERN_PROCARGS2), replaced placeholder at call site
  • crates/sysprims-proc/src/lib.rs — Added test_own_process_has_cmdline test
  • ffi/sysprims-ffi/src/lib.rs — Added descendants and kill-descendants FFI exports
  • ffi/sysprims-ffi/src/proc.rs — FFI implementation for new exports
  • bindings/go/sysprims/proc.go — Go binding functions
  • bindings/go/sysprims/include/sysprims.h — C header for new FFI functions
  • bindings/typescript/sysprims/native/src/lib.rs — N-API native module
  • bindings/typescript/sysprims/src/ffi.ts — TypeScript FFI declarations
  • bindings/typescript/sysprims/src/index.ts — TypeScript public API exports
  • bindings/typescript/sysprims/src/types.ts — TypeScript type definitions

References

  • Bug report: kitfly/.plans/memos/sysprims/processlist-cmdline-truncated.md
  • Fix plan: .plans/active/v0.1.13/process-name-fidelity.md
  • Feature brief: .plans/active/v0.1.13/feature-brief.md
  • ADR-0011: PID Validation Safety

v0.1.12

06 Feb 18:07
9b59b43

Choose a tag to compare

v0.1.12 - Process Tree Operations & Enhanced Discovery

Release Date: 2026-02-06
Status: Process Tree Operations & Enhanced Discovery Release

Summary

This release adds process tree traversal capabilities, ASCII tree visualization, and enhanced filtering for surgical process management. Operators can now inspect process hierarchies, identify runaway descendants, and terminate specific subtrees without affecting parent processes or critical system processes.

Highlights

  • Process Tree Visibility: New descendants command with ASCII art visualization shows instant, human-readable process trees
  • Targeted Cleanup: kill-descendants enables surgical subtree termination with filter support (--cpu-above, --running-for, --name)
  • Age-Based Filtering: --running-for option on all process commands helps distinguish long-running spinners from recent spikes
  • Parent PID Filtering: --ppid option on pstat and kill for filtering by process parent
  • Safety by Design: Filter-based kills always preview unless --yes provided; never targets self, PID 1, parent, or root without --force
  • Depth-Controlled Traversal: --max-levels N limits tree depth (default 1 = direct children only, accepts "all" for full subtree)

Changes

CLI: sysprims descendants Command

New subcommand to list child processes of a given root PID:

# Show direct children only (level 1)
sysprims descendants 7825 --table

# Show 2 levels deep (children + grandchildren)
sysprims descendants 7825 --max-levels 2 --table

# Show full subtree with ASCII art
sysprims descendants 7825 --max-levels all --tree

# Filter by CPU usage
sysprims descendants 7825 --cpu-above 80 --tree

# Filter by process age (long-running spinners)
sysprims descendants 7825 --running-for "1h" --tree

Options:

Option Description
--max-levels <N> Maximum traversal depth (1 = direct children, "all" = full subtree)
--json Output as JSON (default)
--table Human-readable table format (flat, grouped by level)
--tree ASCII art tree with hierarchy visualization
--name <NAME> Filter by process name (substring match)
--user <USER> Filter by username
--cpu-above <PERCENT> Filter by minimum CPU usage
--memory-above <KB> Filter by minimum memory usage
--running-for <DURATION> Filter by minimum process age (e.g., "5s", "1m", "2h")

Example output (ASCII tree):

7825 Electron [0.1% CPU, 160M, 7d18h]
├── 985 VSCodium Helper [0.0% CPU, 63M, 1d13h]
├── 986 VSCodium Helper (Plugin) [0.0% CPU, 84M, 1d13h]
│   ├── 1066 VSCodium Helper (Plugin) [0.0% CPU, 56M, 1d13h]
│   └── 5404 VSCodium Helper (Plugin) [0.0% CPU, 58M, 1d13h]
├── 5495 VSCodium Helper (Renderer) [0.0% CPU, 119M, 16h47m]
└── 5500 VSCodium Helper [0.0% CPU, 60M, 16h47m]

Total: 4 processes in subtree, 4 matched filter

CLI: sysprims kill-descendants Command

Send signals to descendants of a process without affecting the parent or root:

# Preview what would be killed
sysprims kill-descendants 7825 --cpu-above 80 --dry-run

# Kill all high-CPU descendants (requires --yes for filter-based selection)
sysprims kill-descendants 7825 --cpu-above 80 --yes

# Kill direct children only (level 1)
sysprims kill-descendants 7825 --max-levels 1 --yes

# Use SIGKILL for hung processes
sysprims kill-descendants 7825 --cpu-above 90 --signal KILL --yes

# Kill full subtree (all descendants)
sysprims kill-descendants 7825 --max-levels all --yes

Safety behaviors:

  • Preview mode: Filter-based selection defaults to --dry-run unless --yes is explicitly provided
  • Self exclusion: Never targets CLI's own process
  • Parent protection: Never targets the parent process of selected descendants (unless --force)
  • PID 1 protection: Never targets init/launchd (unless --force)
  • Root protection: Never targets system root without --force

Options:

Option Description
--max-levels <N> Maximum traversal depth (same as descendants)
-s, --signal <SIGNAL> Signal name or number (default: TERM)
--name <NAME> Filter by process name
--user <USER> Filter by username
--cpu-above <PERCENT> Filter by minimum CPU usage
--memory-above <KB> Filter by minimum memory usage
--running-for <DURATION> Filter by minimum process age
--dry-run Print matched targets but do not send signals
--yes Proceed with kill (required for filter-based selection)
--force Proceed even if CLI safety checks would normally refuse
--json Output as JSON

CLI: Enhanced sysprims pstat Options

New filter options for process discovery:

# Filter by parent PID
sysprims pstat --ppid 7825 --table

# Filter by process age (long-running processes only)
sysprims pstat --cpu-above 80 --running-for "1h" --table

# Combine multiple filters
sysprims pstat --ppid 7825 --cpu-above 90 --running-for "10m" --table

New filter options:

Option Available on
--ppid <PID> pstat, kill
--running-for <DURATION> pstat, kill, descendants, kill-descendants

CLI: Enhanced sysprims kill Options

The kill command now accepts all filter options in addition to explicit PIDs:

# Kill by parent PID filter
sysprims kill --ppid 7825 --signal TERM

# Kill by combined filters (requires --yes for filter-based selection)
sysprims kill --ppid 7825 --cpu-above 80 --yes

# Preview before killing (dry-run mode)
sysprims kill --ppid 7825 --cpu-above 80 --dry-run

# Force override for protected targets
sysprims kill --ppid 7825 --yes --force

Validation

Process Tree Operations

Tested on macOS arm64 (Darwin 25.2.0):

Descendants command:

$ sysprims descendants 7825 --max-levels 1 --table
--- Level 1 ---
    PID    PPID   CPU%    MEM(KB)    STATE USER             NAME
--------------------------------------------------------------------------------
   985    7825    0.0      62720        R davethompson     VSCodium Helper
   986    7825    0.0      83408        R davethompson     VSCodium Helper (Plugin)
   ... (40 total descendants)

ASCII tree visualization:

$ sysprims descendants 7825 --tree | head -15
7825 Electron [0.1% CPU, 160M, 7d18h]
├── 985 VSCodium Helper [0.0% CPU, 63M, 1d13h]
├── 986 VSCodium Helper (Plugin) [0.0% CPU, 84M, 1d13h]
├── 5495 VSCodium Helper (Renderer) [0.0% CPU, 119M, 16h47m]
└── ...

Kill-descendants safety:

# Parent excluded by default
$ sysprims kill-descendants 7825 --dry-run
# Output: 40 descendants (no parent PID)

# With --force, parent included
$ sysprims kill-descendants 7825 --dry-run --force
# Output: 41 processes (includes parent PID)

Filter Validation

Parent PID filter:

$ sysprims pstat --ppid 7825 --table
# Shows 32 direct children of VSCodium Electron process

Age-based filtering:

# Find processes >90% CPU running >1 hour
$ sysprims pstat --cpu-above 90 --running-for "1h" --table
# Distinguishes long-running spinners from brief spikes

Real-World Use Cases

Scenario: Identify and terminate runaway Electron helper processes

# 1. Find high CPU processes in tree
$ sysprims descendants 7825 --cpu-above 80 --tree

# 2. Preview what would be killed
$ sysprims kill-descendants 7825 --cpu-above 80 --dry-run --json

# 3. Terminate runaway descendants (parent VSCodium survives)
$ sysprims kill-descendants 7825 --cpu-above 80 --yes

Scenario: Chrome renderer runaway

# Chrome has many helper processes; find the one spinning
$ sysprims descendants 67566 --name "Helper (Renderer)" --cpu-above 100 --tree

# Kill just the runaway renderer (not entire browser)
$ sysprims kill-descendants 67566 --name "Helper (Renderer)" --cpu-above 100 --max-levels 2 --yes

Platform Notes

macOS

Process tree traversal works correctly with libproc:

  • Uses proc_pidinfo(PROC_PIDTBSDINFO) for parent-child relationships
  • BFS traversal respects --max-levels depth limit
  • Parent process exclusion enforced for kill-descendants

Age filtering availability:

  • Process start time available via proc_pidinfo()
  • start_time_unix_ms field populated for all processes
  • --running-for filters work on macOS

ASCII tree visualization:

  • Requires terminal supporting box-drawing characters (UTF-8)
  • Falls back gracefully on terminals without tree line support
  • Uses ├──, , └── for tree structure

Linux

Full visibility: /proc/[pid]/stat provides complete process tree without restrictions.
All features work identically to macOS.

Windows

Process tree traversal: Supported via CreateToolhelp32Snapshot.
Depth limiting and filtering work as on Unix.

ASCII tree visualization: Box-drawing characters may not render correctly on some terminals.
Consider using --table or --json on Windows for reliability.

Schema Additions

process-filter.schema.json

New filter fields:

{
  "properties": {
    "ppid": {
      "description": "Filter by parent process ID",
      "type": "integer",
      "minimum": 1
    },
    "running_for_at_least_secs": {
      "description": "Filter by minimum process age in seconds",
      "type": "number",
      "minimum": 0
    }
  }
}

descendants-result.schema.json (NEW)

New schema for descendants command output:

{
  "schema_id": "https://schemas.3leaps.dev/sysprims/process/v1.0.0/descendants-result.schema.json",
  "root_pid": "integer",
  "max_levels": "integer",
  "levels": [
    {
      "level": "integer",
      "processes": [{ /* Process objects */ }]
    }
  ],
  "total_found": "integer",
  "matched_by_filter": "integer"
}

Safety Considerations

PID Validation (ADR-0011)

All tree traversal and kill operations enforce ADR-0011 PID safety:

  • **PI...
Read more

v0.1.11

04 Feb 21:03
45d8fba

Choose a tag to compare

v0.1.11 - macOS Port Discovery & Bun Runtime Support

Release Date: 2026-02-04
Status: macOS Port Discovery & Bun Runtime Support Release

Summary

This release fixes listeningPorts() returning empty results on macOS, adds a new ports CLI command for listing listening port bindings, and enables Bun runtime support for TypeScript bindings.

Highlights

  • macOS Port Discovery Fixed: listeningPorts() now works on macOS
  • New CLI Command: sysprims ports for listing listening port bindings
  • Bun Runtime Support: TypeScript bindings now work under Bun

Changes

CLI: sysprims ports Command

New subcommand to list listening port bindings:

# Table output
sysprims ports --table

# Filter by protocol
sysprims ports --protocol tcp --table

# Filter by specific port (JSON output)
sysprims ports --protocol tcp --local-port 8080 --json

Options:

Option Description
--json Output as JSON (default)
--table Human-readable table format
--protocol <tcp|udp> Filter by protocol
--local-port <PORT> Filter by local port number

Example output (table):

PROTO LOCAL                  STATE        PID NAME
--------------------------------------------------------------------------------
  tcp [::1]:9999             listen     54659 bun
  tcp 0.0.0.0:9000           listen     41721 ssh
  tcp 127.0.0.1:8080         listen     40672 namelens
  ...

Example output (JSON):

{
  "schema_id": "https://schemas.3leaps.dev/sysprims/process/v1.0.0/port-bindings.schema.json",
  "bindings": [
    {
      "protocol": "tcp",
      "local_addr": "127.0.0.1",
      "local_port": 8080,
      "state": "listen",
      "pid": 40672,
      "process": {
        "pid": 40672,
        "name": "namelens",
        "exe_path": "/Users/.../namelens",
        "cmdline": ["namelens"]
      }
    }
  ],
  "warnings": [...]
}

macOS listeningPorts() Fix

The listeningPorts() function was returning empty results on macOS due to incorrect socket fdinfo parsing. This release fixes the underlying issues:

Problem: SDK struct layout mismatch caused proc_pidfdinfo(PROC_PIDFDSOCKETINFO) parsing to fail silently.

Solution:

  1. UID Filtering: Scan current-user processes only

    • Uses proc_pidinfo(PROC_PIDTBSDINFO) to read UID cheaply
    • Skips other users' PIDs (reduces EPERM volume)
    • Significantly improves performance and reduces noise
  2. Heuristic Layout Detection: Handle SDK variations

    • vinfo_stat size varies across macOS SDKs (136 vs 144 bytes)
    • New select_socket_info_layout() tries common sizes and validates
    • Offset-based parsing instead of fixed struct assumptions
  3. Strict TCP Listener Filtering: Only return actual listeners

    • Checks tcpsi_state == TSI_S_LISTEN for TCP sockets
    • UDP bindings included (UDP has no "listen" state)

Before (v0.1.10):

const result = listeningPorts();
// result.bindings.length === 0  (empty!)

After (v0.1.11):

const result = listeningPorts();
// result.bindings.length === 68  (working!)

TypeScript Bindings: Bun Runtime Support

Removed the explicit Bun block from bindings/typescript/sysprims/src/native.ts:

// REMOVED in v0.1.11:
if ((process as unknown as { versions?: { bun?: string } }).versions?.bun) {
  throw new Error(
    "sysprims TypeScript bindings are not yet validated on Bun. " +
      "Run under Node.js or add a fallback path for Bun.",
  );
}

Bun's N-API compatibility is mature enough for production use.

Validation

macOS Port Discovery

Tested on macOS arm64 (Darwin 25.2.0):

$ sysprims ports --protocol tcp --table
PROTO LOCAL                  STATE        PID NAME
--------------------------------------------------------------------------------
  tcp [::1]:9999             listen     54659 bun
  tcp 0.0.0.0:9000           listen     41721 ssh
  tcp 127.0.0.1:8080         listen     40672 namelens
  ... (31 TCP listeners found)

Self-listener test passes:

$ cargo test -p sysprims-proc test_listening_ports_self_listener_tcp
test test_listening_ports_self_listener_tcp ... ok

Bun Runtime

Validated by kitfly team:

Feature Status
Module loading Works
procGet() Works
terminate() Works
listeningPorts() Works (with macOS fix)

Platform Notes

macOS Visibility

Port discovery on macOS is limited to current-user processes due to SIP/TCC restrictions:

  • Visible: Processes owned by the current user
  • Not visible: System processes, other users' processes
  • Warnings: Indicate how many entries were filtered

This is inherent to macOS security model and cannot be bypassed without elevated privileges.

Linux

Full visibility via /proc/net/* - no restrictions for same-user processes.

Windows

Not yet implemented for listeningPorts().

Upgrade Notes

  • No breaking changes
  • macOS users will now see port bindings that were previously invisible
  • Bun users can use sysprims directly without workarounds
  • New ports CLI command available

Files Changed

  • crates/sysprims-cli/src/main.rs - Added ports subcommand (+138 lines)
  • crates/sysprims-proc/src/macos.rs - Fixed socket fdinfo parsing (~200 lines changed)
  • crates/sysprims-proc/tests/port_bindings.rs - Tightened macOS self-listener test
  • bindings/typescript/sysprims/src/native.ts - Removed Bun guard (-6 lines)

References

  • Feature briefs:
    • .plans/active/v0.1.11/01-macos-listening-ports-reliability.md
    • .plans/active/v0.1.11/02-bun-runtime-support.md
  • Commits:
    • b14b68a - fix(proc/macos): make listening port discovery reliable
    • 4ca6469 - fix(proc/macos): harden socket fdinfo parsing
    • 96d8c11 - feat(cli): add ports command

v0.1.10

03 Feb 20:17
78df5fd

Choose a tag to compare

v0.1.10 - 2026-02-03

Status: Go Shared Library Mode Polish Release

Fast-follow polish release improving Go shared-library mode developer experience and clarifying multi-Rust FFI collision guidance.

Highlights

  • sysprims_shared_local Tag: New opt-in build tag for local development workflows
  • Cleaner Default Shared Mode: sysprims_shared no longer references non-existent local paths
  • Clearer Multi-Rust Guidance: README explicitly documents duplicate symbol _rust_eh_personality failure mode

New Build Tag: sysprims_shared_local

For developers who need to link against locally-built shared libraries:

# Local development with custom shared libs
# (libs must be in bindings/go/sysprims/lib-shared/local/<platform>/)
go test -v -tags="sysprims_shared,sysprims_shared_local" ./...

This tag re-enables the local override paths that were previously searched by default, which caused confusing linker warnings when the directory didn't exist.

Cleaner Default: sysprims_shared

The default shared mode now only searches shipped prebuilt libraries:

# Standard shared mode (no local paths searched)
# glibc/macOS/Windows
go test -v -tags=sysprims_shared ./...

# Alpine/musl
go test -v -tags="musl,sysprims_shared" ./...

This eliminates the linker warnings that previously appeared when lib-shared/local/ didn't exist.

Multi-Rust FFI Collision Guidance

The README now explicitly documents the "multiple Rust FFI libs in one Go binary" failure mode:

Symptom: Link errors mentioning duplicate symbols like _rust_eh_personality

Cause: Linking multiple Rust static libraries (via cgo //#cgo LDFLAGS: -l...) in a single Go binary causes duplicate Rust runtime symbols.

Solution: Use sysprims as a shared library:

Platform Build Tags
glibc/macOS/Windows -tags=sysprims_shared
Alpine/musl -tags="musl,sysprims_shared"
Local dev override -tags="sysprims_shared,sysprims_shared_local"

When to Use Shared Mode

Default (static linking):

go test ./...
  • Recommended unless you hit Rust staticlib collisions
  • Links against prebuilt libsysprims_ffi.a static library

Shared mode (avoids duplicate symbols):

# Standard platforms
go test -tags=sysprims_shared ./...

# Alpine/musl
go test -tags="musl,sysprims_shared" ./...
  • Required when linking multiple Rust staticlibs via cgo
  • Links against prebuilt shared library (.so/.dylib/.dll)

Local development override:

go test -tags="sysprims_shared,sysprims_shared_local" ./...
  • Links against locally-built shared library in lib-shared/local/
  • Useful for testing local changes to the FFI layer

Upgrade Notes

  • No breaking changes for existing sysprims_shared workflows using prebuilt libraries
  • If you were relying on lib-shared/local/... implicitly, add the sysprims_shared_local tag explicitly
  • No changes needed for standard consumers using shipped prebuilt libs

References

  • Commit: 3b004b7 - adds sysprims_shared_local, removes local-path warnings, updates docs
  • Go bindings: bindings/go/sysprims/

v0.1.9

03 Feb 15:46
4aa3fd8

Choose a tag to compare

sysprims v0.1.9

GPL-free, cross-platform process utilities with group-by-default tree management.

Installation

Download the appropriate archive for your platform and extract.

Verification

Note: This release is unsigned. Signed checksums will be uploaded after manual signing.

Once signed artifacts are available:

# Verify checksum signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub

# Verify file checksums
shasum -a 256 -c SHA256SUMS

Platform Matrix

Platform CLI FFI Go Bindings
Linux x64 (glibc 2.17+) sysprims-*-linux-amd64.tar.gz In FFI bundle Supported
Linux x64 (musl) sysprims-*-linux-amd64-musl.tar.gz In FFI bundle Supported
Linux arm64 (glibc 2.17+) sysprims-*-linux-arm64.tar.gz In FFI bundle Supported
Linux arm64 (musl) sysprims-*-linux-arm64-musl.tar.gz In FFI bundle Supported
macOS x64 sysprims-*-darwin-amd64.tar.gz In FFI bundle Supported
macOS arm64 sysprims-*-darwin-arm64.tar.gz In FFI bundle Supported
Windows x64 sysprims-*-windows-amd64.zip In FFI bundle Supported
Windows arm64 sysprims-*-windows-arm64.zip In FFI bundle Not supported

Note: Windows arm64 Go bindings are not supported because MinGW does not support the arm64 target.

What's Changed

  • feat(bindings): update Go prebuilt libs for v0.1.8 by @github-actions[bot] in #8
  • feat(bindings): update Go prebuilt libs for v0.1.9 by @github-actions[bot] in #9
  • feat(bindings): update Go prebuilt libs for v0.1.9 by @github-actions[bot] in #10

Full Changelog: v0.1.7...v0.1.9

v0.1.8

31 Jan 22:12

Choose a tag to compare

sysprims v0.1.8

Release Date: 2026-01-29
Status: CLI Tree Termination Release

Summary

This release adds the terminate-tree CLI subcommand for safe, structured termination of existing process trees. Combined with enhanced pstat sampling modes, sysprims now provides a complete workflow for diagnosing and cleaning up runaway processes without GPL-licensed utilities.

Highlights

  • sysprims terminate-tree: Terminate process trees with graceful-then-kill escalation
  • PID Reuse Protection: --require-start-time-ms and --require-exe-path identity guards
  • CLI Safety: Refuses to terminate PID 1, self, or parent without --force
  • pstat Sampling: --sample and --top flags for CPU investigation
  • Documentation: Real-world guide for runaway process diagnosis

New CLI Commands

sysprims terminate-tree

Terminate an existing process tree by PID. This wraps the terminate_tree() library function (added in v0.1.6) with CLI-specific safety guards.

sysprims terminate-tree <PID> [OPTIONS]

Basic Usage

# Terminate process tree rooted at PID 26021
sysprims terminate-tree 26021

# With JSON output for automation
sysprims terminate-tree 26021 --json

PID Reuse Protection (Recommended)

When automating process termination, use identity guards to ensure you're killing the right process:

sysprims terminate-tree 26021 \
  --require-exe-path "/Applications/VSCodium.app/Contents/MacOS/Electron" \
  --require-start-time-ms 1769432792261 \
  --json

If the current process at PID 26021 doesn't match the specified identity, the command fails safely.

Options Reference

Option Default Description
--grace <DURATION> 5s Grace period before escalation
--kill-after <DURATION> 10s Time to wait before sending kill signal
--signal <SIGNAL> TERM Signal for grace period
--kill-signal <SIGNAL> KILL Signal for forced termination
--require-start-time-ms <MS> - Refuse if PID start time doesn't match
--require-exe-path <PATH> - Refuse if PID exe path doesn't match
--force false Override safety checks
--json false Output as JSON

Output Schema

{
  "schema_id": "https://schemas.3leaps.dev/sysprims/process/v1.0.0/terminate-tree-result.schema.json",
  "timestamp": "2026-01-29T21:23:07.677021Z",
  "platform": "macos",
  "pid": 26021,
  "pgid": 26021,
  "signal_sent": 15,
  "escalated": false,
  "exited": true,
  "timed_out": false,
  "tree_kill_reliability": "guaranteed",
  "warnings": []
}
Field Description
signal_sent Signal number used (15=SIGTERM, 9=SIGKILL)
escalated Whether SIGKILL was needed after grace period
exited Process terminated successfully
tree_kill_reliability "guaranteed" if PGID kill was used
warnings Any edge cases encountered

CLI Safety Guards

The CLI includes interactive safety checks that refuse to proceed without --force:

  • PID 1 (init/launchd): System stability risk
  • Self: Would terminate the sysprims process
  • Parent: Would terminate the calling shell/process

These are CLI-specific protections. The underlying library allows these operations for controlled automation scenarios that may legitimately need them.

pstat Sampling Enhancements

New flags for real-time CPU investigation:

# Sample CPU over 250ms interval, show top 5
sysprims pstat --sample 250ms --top 5 --sort cpu --table

# Find VSCodium helpers with >50% CPU (sampled)
sysprims pstat --name "VSCodium Helper" --sample 500ms --cpu-above 50 --json
New Option Description
--sample <DURATION> Compute CPU rate over sampling interval
--top <N> Limit output to top N processes (after filtering)

Without --sample, cpu_percent is a lifetime average which may not reflect current activity. With --sample, you get near-instantaneous CPU usage similar to Activity Monitor.

Surgical vs Tree Termination

This release documents two strategies for handling runaway processes:

Option A: Surgical Strike (Try First)

Kill individual runaway processes while preserving the parent application:

sysprims kill 8436 -s TERM

If SIGTERM is ignored (common with runaway Electron/Node processes):

sysprims kill 8436 -s KILL

Benefits:

  • Preserves parent application windows
  • Minimal disruption
  • No respawn observed in testing

Option B: Tree Termination (If Surgical Fails)

If processes respawn or too many are affected:

sysprims terminate-tree 26021 --require-exe-path "..."

Trade-offs:

  • Terminates parent and ALL descendants
  • Closes all windows managed by that process
  • Guaranteed cleanup

Documentation

New Guide: Runaway Process Diagnosis

docs/guides/runaway-process-diagnosis.md provides a real-world walkthrough:

  1. Finding high-CPU processes with pstat --sample --cpu-above
  2. Identifying process relationships via ppid inspection
  3. Deciding on termination scope (surgical vs tree)
  4. Verifying termination completed successfully
  5. TypeScript library examples for automation

The guide uses a real scenario: nine VSCodium Helper (Plugin) processes consuming 95%+ CPU each, caused by a runaway extension.

Library vs CLI Protections

Protection Library CLI
PID 0 rejected Yes Yes (via library)
PID > MAX_SAFE_PID rejected Yes Yes (via library)
Don't group-kill own PGID Yes Yes (via library)
Refuse PID 1 No Yes (requires --force)
Refuse self No Yes (requires --force)
Refuse parent No Yes (requires --force)

The library provides safety against dangerous POSIX semantics. The CLI adds interactive footgun protections for common mistakes.

Migration Guide

For CLI Users

No breaking changes. The terminate-tree subcommand is additive.

If you were using sysprims timeout solely for tree termination of external processes, you can now use terminate-tree directly:

# Before: awkward workaround
sysprims timeout --pid-file /tmp/pid 0s -- sleep infinity &
# ... then read PID and terminate

# After: direct tree termination
sysprims terminate-tree <PID>

For Library Users

No changes required. The CLI wraps existing sysprims_timeout::terminate_tree().

Platform Support

Platform terminate-tree pstat --sample
Linux x64 (glibc) Yes Yes
Linux x64 (musl) Yes Yes
Linux arm64 (glibc) Yes Yes
Linux arm64 (musl) Yes Yes
macOS arm64 Yes Yes
Windows x64 Yes Yes
Windows arm64 Yes Yes

Known Limitations

  • Workspace identification: Determining which IDE window a child process belongs to requires OS tools (lsof on Unix). Future releases will add sysprims fds for self-contained investigation.
  • SIGTERM may be ignored: Runaway Electron/Node processes sometimes ignore SIGTERM. The guide documents escalation to SIGKILL.

Coming in v0.1.9

  • sysprims fds: Open file descriptor inspection (Linux/macOS first; Windows NotSupported)
  • Multi-PID kill: sysprims kill <PID> <PID> ... batch operations

Verification

Verify this release with the signed checksums:

# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.8/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.8/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.8/sysprims-minisign.pub

# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub

# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missing

Changes

Added

  • CLI: sysprims terminate-tree - tree termination for existing processes

    • --require-start-time-ms, --require-exe-path for PID reuse protection
    • --grace, --kill-after, --signal, --kill-signal timing/signal options
    • --force to override CLI safety guards
    • --json for machine-readable output
  • CLI: pstat sampling mode

    • --sample <DURATION> for CPU sampling over interval
    • --top <N> for limiting output
  • Documentation

    • docs/guides/runaway-process-diagnosis.md - real-world investigation guide

v0.1.7

28 Jan 16:01

Choose a tag to compare

sysprims v0.1.7

Release Date: 2026-01-26
Status: TypeScript Bindings Infrastructure Release

Summary

This release migrates TypeScript bindings from koffi FFI to a Node-API (N-API) native addon via napi-rs. The primary user-facing outcome: TypeScript bindings now work in Alpine/musl containers, removing a key blocker for container-based deployments.

Highlights

  • Node-API Migration: TypeScript bindings use napi-rs instead of koffi + vendored shared libraries
  • Alpine/musl Support: Linux musl containers (including Alpine) now supported
  • No API Changes: Existing TypeScript imports and function calls remain unchanged
  • npm Publishing Deferred: Prebuilt npm packages planned for future release

Architecture Change

Previous Model (v0.1.4-v0.1.6)

TypeScript code
    ↓
koffi.load() FFI
    ↓
Vendored C-ABI shared library (_lib/<platform>/libsysprims_ffi.*)
    ↓
Rust FFI exports (sysprims-ffi crate)

Limitations:

  • koffi's native loading had glibc dependencies that prevented Alpine/musl usage
  • Vendored shared libraries added distribution complexity
  • Multiple failure modes when loading native code

New Model (v0.1.7+)

TypeScript code
    ↓
require() Node-API addon
    ↓
.node binary built with napi-rs (native/<platform>/)
    ↓
Rust implementation (direct, no C-ABI indirection)

Benefits:

  • Node-API is designed for native addon portability
  • napi-rs provides Rust-native Node-API bindings
  • Single binary per platform (.node file)
  • Works on glibc and musl

Migration Guide

For Application Code

No changes required. The JavaScript API surface is unchanged:

// These imports and calls work identically in v0.1.6 and v0.1.7
import { procGet, processList, terminateTree, spawnInGroup } from '@3leaps/sysprims';

const info = procGet(process.pid);
const procs = processList({ name_contains: 'node' });

For Build/Deploy

Installing from Git Checkout (Current)

When installing from a git checkout or local path, the addon must be built from source:

# Ensure Rust toolchain is available
rustc --version

# Install and build
cd bindings/typescript/sysprims
npm install
npm run build:native

Build requirements:

  • Rust toolchain (1.81+)
  • C/C++ compiler (for napi-rs build)
  • Node.js 18+

Installing from npm (Future)

When npm publishing is enabled, prebuilt platform packages will be installed automatically:

npm install @3leaps/sysprims
# Prebuilt .node binary selected based on platform

No build tools required for npm installs with prebuilds.

For Container Images

Alpine is now supported. Example Dockerfile:

FROM node:20-alpine

# For building from source (until npm prebuilds are available)
RUN apk add --no-cache rust cargo build-base

WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm run build:native

COPY . .
CMD ["node", "index.js"]

Once npm prebuilds are available, the Rust/build-base dependencies will be unnecessary.

What Changed (Implementation Detail)

Aspect v0.1.6 (koffi) v0.1.7 (N-API)
Native loading koffi.load() C-ABI shared lib require() N-API .node addon
Library location _lib/<platform>/libsysprims_ffi.* native/<platform>/sysprims.*.node
Build system Prebuilt libs vendored in package napi-rs build from Rust source
Error handling FFI returns error code, JS checks FFI returns { code, json?, message? }
Alpine support No (glibc required) Yes

Error Handling Internals

The N-API layer returns structured results:

// Internal FFI result shape
interface NativeResult {
  code: number;      // 0 = success, >0 = error code
  json?: string;     // JSON payload on success
  message?: string;  // Error message on failure
}

The TypeScript wrapper interprets these and throws SysprimsError with the same error codes as before, so error handling code is unchanged.

Platform Support

Platform CLI Go Bindings TypeScript Bindings
Linux x64 (glibc) yes yes yes
Linux x64 (musl) yes yes yes (new)
Linux arm64 (glibc) yes yes yes
Linux arm64 (musl) yes yes yes (new)
macOS x64 no no no
macOS arm64 yes yes yes
Windows x64 yes yes yes

CI Validation

The TypeScript bindings are validated by:

  1. TypeScript Bindings workflow (typescript-bindings.yml)

    • Runs on macOS, Windows, Linux (glibc)
    • Includes Alpine/musl container lane
    • Tests all exported functions
  2. Validate Release workflow (validate-release.yml)

    • Post-tag validation
    • Includes TypeScript on all platforms including Alpine

Adoption Recommendations

Initial Rollout

  1. Pin to exact version: "@3leaps/sysprims": "0.1.7"
  2. Add smoke test: Verify addon loads before relying on it
    import { procGet } from '@3leaps/sysprims';
    
    // Smoke test at startup
    try {
      procGet(process.pid);
    } catch (e) {
      console.error('sysprims addon failed to load:', e);
      // Fall back to alternative implementation
    }

Fallback Strategy

Even with N-API, native addons can fail in edge cases (locked-down environments, unusual Node builds). Consider keeping fallback implementations:

let sysprims: typeof import('@3leaps/sysprims') | null = null;

try {
  sysprims = require('@3leaps/sysprims');
} catch {
  console.warn('sysprims unavailable, using fallbacks');
}

export function terminateProcess(pid: number): void {
  if (sysprims) {
    sysprims.terminate(pid);
  } else {
    process.kill(pid, 'SIGTERM');
  }
}

Bun Runtime

Bun has N-API compatibility, but behavior may differ from Node.js. Validate sysprims in Bun-specific test lanes before production use. If issues arise, gate sysprims to Node-only entrypoints.

Changes

Changed

  • TypeScript Bindings Architecture (bindings/typescript/sysprims/)
    • Migrated from koffi + vendored C-ABI shared libraries to Node-API (N-API) native addon via napi-rs
    • Prebuilt .node binaries loaded from native/ directory
    • FFI returns { code, json?, message? } internally; JS layer throws SysprimsError with same error codes

Added

  • Linux musl/Alpine Support (TypeScript)
    • TypeScript bindings now work in Alpine-based containers
    • Removes the "glibc-only" limitation from v0.1.4-v0.1.6

Notes

  • No API Changes: Existing TypeScript imports and function calls remain unchanged
  • Build from Source: Installing from git checkout requires Rust toolchain
  • npm Prebuilds: Deferred to future release pending consumer validation

Verification

Verify this release with the signed checksums:

# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.7/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.7/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.7/sysprims-minisign.pub

# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub

# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missing

Next Release

v0.1.8+ will continue toward:

  • npm publishing with prebuilt platform packages
  • Python bindings (cffi + wheel packaging)
  • Timeout API for TypeScript (complex config struct)

v0.1.6

25 Jan 22:34
0de1c7e

Choose a tag to compare

sysprims v0.1.6

Release Date: 2026-01-25
Status: Supervisor & Job Manager Primitives Release

Summary

This release delivers process management primitives for long-running supervisors and job managers. Teams building systems like gonimbus or rampart lifecycle can now spawn kill-tree-safe jobs, detect PID reuse, and cleanly terminate process trees—without coupling to the timeout API.

Highlights

  • PID Reuse Guard: New start_time_unix_ms and exe_path fields enable detection of PID reuse
  • Spawn In Group: Create processes in a new process group (Unix) or Job Object (Windows)
  • Wait With Timeout: Poll for process exit with configurable timeout
  • Terminate Tree: Graceful-then-kill tree termination as a standalone primitive
  • Windows Documentation: Job Object registry behavior documented

New Primitives

Primitive Rust FFI Go TypeScript
Process identity ProcessInfo sysprims_proc_get ProcessGet procGet
Spawn in group spawn_in_group() sysprims_spawn_in_group SpawnInGroup spawnInGroup
Wait PID with timeout wait_pid() sysprims_proc_wait_pid WaitPID waitPID
Terminate tree terminate_tree() sysprims_terminate_tree TerminateTree terminateTree

Process Identity (PID Reuse Guard)

Problem

Long-running supervisors store PIDs and later attempt to signal them. PID reuse can cause supervisors to accidentally target the wrong process after the original exits.

Solution

The ProcessInfo struct now includes start_time_unix_ms and exe_path (when obtainable), allowing consumers to distinguish between "same PID, same process" and "same PID, different process" (PID reuse).

Rust Usage

use sysprims_proc::get_process;

// At job creation, store identity
let info = get_process(pid)?;
let job_record = JobRecord {
    pid: info.pid,
    start_time: info.start_time_unix_ms,
    cmdline: info.cmdline.clone(),
};

// Later, before signaling
let current = get_process(job_record.pid)?;
if current.start_time_unix_ms != job_record.start_time {
    log::warn!("PID {} was reused, not signaling", job_record.pid);
    return Ok(JobStatus::Stale);
}

Go Usage

import "github.com/3leaps/sysprims/bindings/go/sysprims"

// At job creation
info, _ := sysprims.ProcessGet(pid)
jobRecord := JobRecord{
    PID:       info.PID,
    StartTime: info.StartTimeUnixMs,
}

// Later, before signaling
current, err := sysprims.ProcessGet(jobRecord.PID)
if err != nil || current.StartTimeUnixMs != jobRecord.StartTime {
    log.Printf("PID %d was reused or gone", jobRecord.PID)
    return
}

TypeScript Usage

import { procGet } from '@3leaps/sysprims';

// At job creation
const info = procGet(pid);
const jobRecord = {
  pid: info.pid,
  startTime: info.start_time_unix_ms,
};

// Later, before signaling
const current = procGet(jobRecord.pid);
if (current.start_time_unix_ms !== jobRecord.startTime) {
  console.warn(`PID ${jobRecord.pid} was reused, not signaling`);
  return;
}

Platform Notes

Platform Source Reliability
Linux /proc/<pid>/stat starttime + boot time High
macOS proc_pidinfo / libproc APIs High
Windows Process creation time via Win32 High

The field is optional and omitted when not obtainable due to permissions.

Spawn In Group

Problem

Supervisors want "kill-tree safe" jobs without using run_with_timeout. Today, reliable grouping is mainly exercised through the timeout API.

Solution

spawn_in_group spawns a child process in a new process group (Unix) or Job Object (Windows), returning identifiers needed for later tree termination.

Rust Usage

use sysprims_timeout::{spawn_in_group, SpawnInGroupConfig};

let outcome = spawn_in_group(SpawnInGroupConfig {
    argv: vec!["./worker.sh".into(), "--id".into(), "42".into()],
    cwd: Some("/app".into()),
    env: Some(vec![("LOG_LEVEL".into(), "debug".into())]),
})?;

println!("Spawned PID {}", outcome.pid);
#[cfg(unix)]
println!("Process group: {}", outcome.pgid.unwrap());
println!("Tree kill reliability: {:?}", outcome.tree_kill_reliability);

Go Usage

import "github.com/3leaps/sysprims/bindings/go/sysprims"

config := sysprims.SpawnInGroupConfig{
    Argv: []string{"./worker.sh", "--id", "42"},
    Cwd:  "/app",
    Env:  map[string]string{"LOG_LEVEL": "debug"},
}

outcome, err := sysprims.SpawnInGroup(config)
if err != nil {
    log.Fatal(err)
}

log.Printf("Spawned PID %d", outcome.PID)
// outcome.PGID is nil on Windows

TypeScript Usage

import { spawnInGroup } from '@3leaps/sysprims';

const outcome = spawnInGroup({
  argv: ['./worker.sh', '--id', '42'],
  cwd: '/app',
  env: { LOG_LEVEL: 'debug' },
});

console.log(`Spawned PID ${outcome.pid}`);
// outcome.pgid is null on Windows

SpawnInGroupResult Fields

Field Type Description
schema_id string Schema identifier
timestamp string RFC3339 timestamp
platform string Platform identifier
pid u32 Child process ID
pgid u32? Process group ID (Unix only; null on Windows)
tree_kill_reliability string "guaranteed" or "best_effort"
warnings string[] Platform-specific warnings

Platform Notes

  • Unix: Uses setpgid(0, 0) to create new process group
  • Windows: Creates Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
  • Degradation: If Job Object creation fails (nested jobs, privilege limits), returns tree_kill_reliability: best_effort

Wait PID With Timeout

Problem

Graceful stop flows need to wait for process exit without blocking forever:

  1. Send TERM
  2. Wait up to N seconds
  3. Send KILL if still running

Solution

wait_pid polls for process exit with configurable timeout.

Rust Usage

use sysprims_proc::wait_pid;
use std::time::Duration;

// Wait up to 10 seconds for exit
let outcome = wait_pid(pid, Duration::from_secs(10))?;

if outcome.timed_out {
    println!("Process {} did not exit, escalating to SIGKILL", pid);
} else if let Some(code) = outcome.exit_code {
    println!("Process {} exited with code {}", pid, code);
}

Go Usage

import (
    "time"
    "github.com/3leaps/sysprims/bindings/go/sysprims"
)

outcome, err := sysprims.WaitPID(pid, 10*time.Second)
if err != nil {
    log.Fatal(err)
}

if outcome.TimedOut {
    log.Printf("Process %d did not exit, escalating", pid)
} else {
    log.Printf("Process %d exited with code %d", pid, outcome.ExitCode)
}

TypeScript Usage

import { waitPID } from '@3leaps/sysprims';

const outcome = waitPID(pid, 10000); // 10 seconds in ms

if (outcome.timed_out) {
  console.log(`Process ${pid} did not exit, escalating`);
} else {
  console.log(`Process ${pid} exited with code ${outcome.exit_code}`);
}

WaitPidResult Fields

Field Type Description
schema_id string Schema identifier
timestamp string RFC3339 timestamp
platform string Platform identifier
pid u32 PID waited on
exited bool True if process exited
timed_out bool True if timeout elapsed before exit
exit_code i32? Exit code (if exited and obtainable)
warnings string[] Platform-specific warnings

Platform Notes

  • Unix: Polling via kill(pid, 0) (works for arbitrary PIDs; not parent-specific)
  • Windows: OpenProcess + WaitForSingleObject (best-effort)

Terminate Tree

Problem

Consumers want a single "stop job" primitive that:

  1. Attempts graceful termination
  2. Waits for exit
  3. Escalates to kill
  4. Reports what happened

Today this is coupled to run_with_timeout's spawned command management.

Solution

terminate_tree provides one-call process tree termination, usable for arbitrary PIDs.

Rust Usage

use sysprims_timeout::{terminate_tree, TerminateTreeConfig};

let outcome = terminate_tree(pid, TerminateTreeConfig {
    grace_timeout_ms: 5000,
    kill_timeout_ms: 2000,
    ..Default::default()
})?;

println!("Grace sent: {}", outcome.grace_sent);
println!("Escalated to kill: {}", outcome.escalated);
println!("Tree kill reliability: {:?}", outcome.tree_kill_reliability);

for warning in &outcome.warnings {
    eprintln!("Warning: {}", warning);
}

Go Usage

import "github.com/3leaps/sysprims/bindings/go/sysprims"

config := sysprims.TerminateTreeConfig{
    GraceTimeoutMs: 5000,
    KillTimeoutMs:  2000,
}

outcome, err := sysprims.TerminateTree(pid, config)
if err != nil {
    log.Fatal(err)
}

if outcome.Escalated {
    log.Printf("Had to escalate to SIGKILL")
}

TypeScript Usage

import { terminateTree } from '@3leaps/sysprims';

const outcome = terminateTree(pid, {
  grace_timeout_ms: 5000,
});

if (outcome.escalated) {
  console.log('Had to escalate to kill');
}

for (const warning of outcome.warnings) {
  console.warn(warning);
}

TerminateTreeConfig Fields

Field Type Default Description
signal signal SIGTERM Initial termination signal
grace_timeout_ms u64 10000 Wait time before escalation (milliseconds)
kill_signal signal SIGKILL Escalation signal
kill_timeout_ms u64 2000 Wait time after kill signal (milliseconds)

TerminateTreeResult Fields

Field Type Description
schema_id string Schema identifier
timestamp string RFC3339 timestamp
platform string Platform identifier
pid u32 PID that was terminated
pgid u32? Process group ID (Unix only)
signal_sent i32 Signal sent for graceful termination
kill_signal i32? Kill signal (if escalated)
escalated bool ...
Read more

v0.1.5

24 Jan 22:43

Choose a tag to compare

sysprims v0.1.5

Release Date: 2026-01-24
Status: TypeScript Bindings Parity Release

Summary

Node.js developers now have access to process inspection, port mapping, and signal APIs. This release achieves parity with Go bindings for these core surfaces (proc/ports/signals).

Highlights

  • TypeScript Parity: Process listing, port inspection, and signal operations
  • Full Type Definitions: All schemas have corresponding TypeScript interfaces
  • Windows Stability: Signal tests no longer flaky on Windows CI
  • CI Improvements: Separated binding validation from release validation

TypeScript API

Installation

npm install @3leaps/sysprims

New Functions

Function Description
processList(filter?) List running processes with optional filtering
listeningPorts(filter?) Map listening ports to owning processes
signalSend(pid, signal) Send signal to a process
signalSendGroup(pgid, signal) Send signal to a process group (Unix only)
terminate(pid) Graceful termination (SIGTERM on Unix; TerminateProcess on Windows - immediate, not graceful)
forceKill(pid) Immediate kill (SIGKILL on Unix; TerminateProcess on Windows)

Existing Functions (from v0.1.4)

Function Description
procGet(pid) Get process info by PID
selfPGID() Get current process group ID (Unix only)
selfSID() Get current session ID (Unix only)

Usage Examples

import {
  processList,
  listeningPorts,
  terminate,
  forceKill,
  signalSend
} from '@3leaps/sysprims';

// List all processes
const all = processList();
console.log(`Found ${all.processes.length} processes`);

// Filter by name (case-insensitive substring match)
const nginx = processList({ name_contains: "nginx" });
for (const proc of nginx.processes) {
  console.log(`${proc.pid}: ${proc.name} (${proc.cpu_percent}% CPU)`);
}

// Filter by multiple criteria (AND logic)
const heavy = processList({
  cpu_above: 50,
  memory_above_kb: 100000
});

// Find what's listening on port 8080
const http = listeningPorts({ local_port: 8080 });
for (const binding of http.bindings) {
  console.log(`Port ${binding.local_port}: PID ${binding.pid} (${binding.process?.name})`);
}

// Filter by protocol
const tcpPorts = listeningPorts({ protocol: "tcp" });

// Gracefully terminate a process
terminate(1234);

// Force kill if graceful termination doesn't work
forceKill(1234);

// Send specific signal (Unix semantics; limited support on Windows)
signalSend(1234, 15);  // SIGTERM

Filter Options

ProcessFilter (for processList):

  • name_contains: Substring match (case-insensitive)
  • name_equals: Exact name match
  • user_equals: Filter by username
  • pid_in: Array of PIDs to include
  • state_in: Array of process states ("running", "sleeping", "stopped", "zombie", "unknown")
  • cpu_above: Minimum CPU percentage (0-100)
  • memory_above_kb: Minimum memory in KB

PortFilter (for listeningPorts):

  • protocol: "tcp" or "udp"
  • local_port: Specific port number

Requirements

  • Node.js 18+
  • glibc-based Linux (musl/Alpine not supported)

Changes

Added

  • TypeScript Bindings Parity (bindings/typescript/sysprims/)

    • processList(filter?) - list processes with optional filtering
    • listeningPorts(filter?) - port-to-PID mapping
    • signalSend(pid, signal) - send signal to process
    • signalSendGroup(pgid, signal) - send signal to process group (Unix)
    • terminate(pid) - graceful termination
    • forceKill(pid) - immediate kill
    • Full TypeScript type definitions for ProcessFilter, PortFilter, ProcessSnapshot, PortBindingsSnapshot
  • CI Improvements

    • Separated binding validation from release validation (validate-release.yml)
    • Clarified Go module tagging requirements in release validation

Changed

  • Go Prebuilt Libraries
    • Updated all 7 platform libraries for v0.1.5

Fixed

  • Windows Signal Tests
    • Signal tests now use deterministic patterns: reject pid=0, spawn-and-kill for terminate/forceKill
    • Eliminates flakiness where arbitrary PIDs may exist on CI runners

Platforms

Platform CLI Go Bindings TypeScript Bindings
Linux x64 (glibc) yes yes yes
Linux x64 (musl) yes yes no
Linux arm64 (glibc) yes yes yes
Linux arm64 (musl) yes yes no
macOS x64 yes yes yes
macOS arm64 yes yes yes
Windows x64 yes yes yes

Note: TypeScript bindings require glibc. Linux musl (Alpine) is not supported.

Verification

Verify this release with the signed checksums:

# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.5/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.5/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.5/sysprims-minisign.pub

# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub

# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missing

Next Release

v0.1.6+ will continue toward:

  • Extended self-introspection surface (self_info API)
  • Python bindings (cffi + wheel packaging)
  • Timeout API for TypeScript (complex config struct)

v0.1.4

23 Jan 14:43

Choose a tag to compare

sysprims v0.1.4

Release Date: 2026-01-22
Status: TypeScript Language Bindings Release

Summary

Node.js developers can now integrate sysprims directly. This release delivers koffi-based TypeScript bindings with cross-platform support, continuing the language bindings story after Go (v0.1.3).

Highlights

  • TypeScript Bindings: First-class Node.js support via koffi FFI
  • Cross-Platform: linux-amd64, linux-arm64, darwin-arm64, windows-amd64
  • ABI Verification: Library loader validates ABI version at startup
  • CI Coverage: Native ARM64 Linux testing added to CI matrix

TypeScript Bindings

Installation

npm install @3leaps/sysprims

Usage

import { procGet, selfPGID, selfSID } from '@3leaps/sysprims';

// Get process info by PID
const proc = procGet(process.pid);
console.log(`Process ${proc.pid}: ${proc.name}`);

// Get current process group/session IDs (Unix)
const pgid = selfPGID();
const sid = selfSID();

API

Function Description
procGet(pid) Get process info by PID (returns parsed JSON)
selfPGID() Get current process group ID (Unix only)
selfSID() Get current session ID (Unix only)

Requirements

  • Node.js 18+
  • glibc-based Linux (musl/Alpine not supported)

How It Works

The TypeScript bindings use koffi to call the sysprims C-ABI shared library. At load time, the binding:

  1. Detects the current platform
  2. Loads the appropriate shared library from _lib/<platform>/
  3. Verifies ABI version matches expected value
  4. Exposes typed functions to JavaScript

Changes

Added

  • TypeScript Bindings (bindings/typescript/sysprims/)

    • koffi-based FFI for Node.js 18+
    • Cross-platform: linux-amd64, linux-arm64, darwin-arm64, windows-amd64
    • ABI version verification on library load
    • CI validates TypeScript bindings on all supported platforms
  • Linux ARM64 CI Coverage

    • Added linux-arm64 runner to CI matrix for native ARM64 testing

Changed

  • CI Platform Matrix
    • Replaced darwin-amd64 (Intel Mac) with linux-arm64
    • Intel Mac runners deprecated by GitHub Actions
    • TypeScript CI: linux-amd64, linux-arm64, darwin-arm64, windows-amd64

Fixed

  • Windows TypeScript Tests

    • Cross-platform build scripts replace Unix-only shell commands
    • npm run test:ci now works on Windows runners
  • Parallel Test Flakiness

    • Added atomic counter to unique_marker() in tree_escape tests
    • Prevents collisions when tests run in parallel with same PID/timestamp

Platforms

Platform CLI Go Bindings TypeScript Bindings
Linux x64 (glibc) yes yes yes
Linux x64 (musl) yes yes no
Linux arm64 (glibc) yes yes yes
Linux arm64 (musl) yes yes no
macOS x64 yes yes no (deprecated)
macOS arm64 yes yes yes
Windows x64 yes yes yes

Note: TypeScript bindings require glibc. Linux musl (Alpine) is not supported.

Verification

Verify this release with the signed checksums:

# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.4/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.4/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.4/sysprims-minisign.pub

# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub

# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missing

Next Release

v0.1.5+ will continue toward:

  • Python bindings (cffi + wheel packaging)
  • Additional TypeScript API surface