Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bgrun

CI License: MIT

Run long-running background tasks easily. Perfect for development servers, test suites, and AI agent automation loops.

bgrun manages your background tasks so you can keep working without terminal lockups. Start servers, track their readiness, tail logs, and shut them down; all over an automatic background daemon using structured JSON payloads.

Install

# Quick install (Linux)
curl -fsSL https://bethropolis.github.io/bgrun/install.sh | sh

# AUR (Arch Linux)
yay -S bgrun-bin

# Homebrew (Linux)
brew tap bethropolis/tap && brew install --cask bethropolis/tap/bgrun

# From source
./install.sh   # requires Rust toolchain

Quick start

1. Run a Process

Launch a process in the background and tell bgrun exactly when it is ready to handle traffic:

bgrun run --name dev-server --ready-when-port 3000 "npm run dev"

2. Coordinate Your Workflow

Block execution only until the server is actually ready, inspect its logs, or terminate it cleanly:

# Block until ready (not a must!)
bgrun wait dev-server --timeout 30s

# Check on-demand logs
bgrun tail dev-server --lines 10

# Clean up
bgrun kill dev-server

Commands

CommandPurpose
bgrun run [flags] <cmd...>Start a background job
bgrun list [--workspace <ws>]List all jobs
bgrun status <id>Get job state
bgrun wait <id> --timeout <d>Block until ready
bgrun tail <id> [--digest] [--level <l>]Show logs
bgrun diff <id>New log lines since last call
bgrun send <id> <data>Write to stdin
bgrun stats <id>Show CPU/RSS/uptime
bgrun kill <id> [--workspace <ws>]Terminate job(s)
bgrun attach <id>Attach to PTY job interactively
bgrun expect <id> <pattern>Wait for log line matching pattern
bgrun schema <command>Print JSON Schema for command args
bgrun run-group <name> [name...]Start multiple named jobs in parallel
bgrun screen <id> [--lines N]Show last N lines from in-memory ring buffer
bgrun clean [--workspace <ws>]Remove all terminated jobs
bgrun skill install <dir>Install embedded skill bundle

Docs

Crate layout

CrateRole
bgrun-protoShared types, no I/O
bgrun-coreJob state machine, config parser
bgrun-daemonSpawns/monitors/kills processes, serves CLI
bgrun-cliUser-facing CLI

License

MIT

Architecture

bgrun is split into 4 crates with a strict dependency graph. The daemon and CLI never link directly — they communicate over a Unix socket using a plain-text NDJSON protocol.

Crate layout

bgrun Architecture Diagram

  • bgrun-proto — zero I/O. Types only. Any crate can depend on it.
  • bgrun-core — zero I/O. Pure logic and data structures. Depends only on bgrun-proto.
  • bgrun-cli — depends on bgrun-proto + bgrun-core. Talks to daemon over Unix socket.
  • bgrun-daemon — depends on bgrun-proto + bgrun-core. Full daemon with tokio, nix, tracing.

The CLI never imports bgrun-daemon. Path utilities (socket_path, state_dir) are in bgrun-proto::paths so both crates share them without coupling.


Daemon lifecycle

Startup

  1. The CLI detects whether the daemon socket exists.
  2. If missing, it spawns bgrun-daemon as a child process (no arguments).
  3. The daemon process forks itself:
    • The parent exits immediately, returning control to the CLI.
    • The child continues as the actual daemon.
  4. The child calls setsid() to create a new session and detach from the terminal.
  5. BGRUN_DAEMONIZED=1 is set to prevent recursive double-fork.

Socket setup

The daemon binds a Unix domain socket at $XDG_RUNTIME_DIR/bgrun/daemon.sock (or /tmp/bgrun-$UID/daemon.sock as fallback). It listens in a tokio accept loop, spawning a new task per connection.

Orphan re-adoption

On startup, the daemon reads all persisted jobs from $XDG_DATA_DIR/bgrun/jobs/$ID/:

  1. If PID is alive (kill(pid, 0) succeeds): the job is re-inserted into the in-memory store and a background task monitors it every 2 seconds.
  2. If PID is dead: the job is marked Crashed in status.json.

This ensures no monitoring gap after a daemon restart.

Shutdown

The daemon runs until killed (pkill bgrun-daemon) or an idle timeout expires. On restart via the CLI, the new instance re-adopts orphaned children as described above.

Reactive idle auto-shutdown

The daemon spawns a background monitor that uses tokio::sync::Notify for zero-CPU idle polling:

  1. A global LIFECYCLE_NOTIFY static is defined in runner.rs.
  2. Every job spawn (spawn_job, spawn_pty_job) and job exit (handle_job_exit) calls .notify_one().
  3. The monitor loop:
    • Active jobs > 0: parks indefinitely on .notified().await — 0 CPU usage.
    • Active jobs = 0: races .notified() against BGRUN_IDLE_TIMEOUT (default 60s).
  4. On timeout expiry, the socket file is deleted and the process exits.

This replaces any periodic polling approach, ensuring the daemon consumes no CPU while idle.


Protocol

The CLI and daemon communicate over a Unix socket using NDJSON (Newline-Delimited JSON).

Request

{"id":"req-uuid","command":"Run","args":{"cmd":["sleep","300"],"name":"sleeper",...}}

One JSON object per line. The command field uses tagged enum serialization (serde’s #[serde(tag = "command", content = "args")]) so all commands share the same outer envelope.

Response

{"id":"req-uuid","ok":true,"data":{"id":"abc123","state":"running",...}}
{"id":"req-uuid","ok":false,"error":"job not found"}

Every request gets exactly one response line. The response format is:

  • req_id — matches the request’s id
  • ok — boolean success indicator
  • data — command-specific payload (only when ok is true)
  • error — error message string (only when ok is false)

Commands

CommandRequest argsResponse data
RunRunArgs { cmd, name, workspace, readiness, restart, pty, max_runtime_ms, max_rss_mb, env, after, cwd, pty_cols, pty_rows }JobRecord
RunGroup{ jobs: Vec<RunArgs> }Vec<JobRecord>
Status{ id }JobStatus
List{ workspace? }Vec<JobRecord>
Kill{ id?, workspace? }{ killed: Vec<String> }
Tail{ id, lines, digest, level?, strip_ansi }{ lines: Vec<LogLine> } or LogDigest
Diff{ id, lines?, strip_ansi }{ cursor: u64, lines: Vec<LogLine> }
Wait{ id, timeout_ms }WaitResult { ready: bool, elapsed_ms: u64 }
Send{ id, data }{ ok: true }
Stats{ id }ResourceStats { cpu_pct, rss_mb, uptime_secs }
Attach{ id }hijacks socket for raw byte streaming
Expect{ id, pattern, is_regex, timeout_ms }{ matched: bool, line_number, content }
ResizePty{ id, cols, rows }{ resized: true }

Process lifecycle

Spawn (runner.rs)

  1. Idempotency check: if a named job already exists and is alive, return its record.
  2. Dependency wait: if after is set, poll the store until the named job reaches Ready, Exited, Crashed, or Killed (120s timeout).
  3. Spawn: tokio::process::Command with piped stdin/stdout/stderr, process group 0.
  4. Output capture: stdout and stderr are piped to async tasks that write to stdout.log with log rotation at 50MB.
  5. Stdin handle: stored in a global HashMap<String, ChildStdin> keyed by job ID.
  6. Ready check: if readiness is set, spawn a background task that polls every 200ms up to 60s.
  7. Max runtime: if max_runtime_ms is set, a tokio sleep task fires after the duration, killing the job if still alive.
  8. Memory limit: if max_rss_mb is set, a background task polls RSS every 1s and kills the job if exceeded.
  9. Lifecycle notify: LIFECYCLE_NOTIFY.notify_one() is called to wake the auto-shutdown monitor.
  10. Persist: write meta.json (full JobRecord) and status.json to disk.

Monitor

After spawn, the daemon spawns monitor_job() which:

  1. Awaits the child process exit.
  2. Determines exit type (SIGKILL → Crashed, non-zero exit → Crashed, zero exit → Exited).
  3. If restart = OnCrash and the process crashed, spawns a new instance after backoff_ms delay.
  4. Writes status.json.

Kill

  1. Send SIGTERM to the process group (killpg).
  2. Wait up to 5 seconds.
  3. If still alive, send SIGKILL to the process group.
  4. Transition state to Killed.

Readiness system

Four checker implementations, all implementing the ReadinessChecker async trait:

StrategyCheckerMechanism
LogPattern("string")LogPatternCheckerReads the log file from last offset, searches for substring. Offset-tracked to avoid re-scanning.
TcpPort(3000)TcpPortCheckerTcpStream::connect("127.0.0.1:3000")
HttpPoll("http://...")HttpPollCheckerreqwest::GET, 500ms timeout, checks for 2xx
FileExists("/path")FileExistsCheckertokio::fs::metadata()

The readiness_loop polls the checker every 200ms. Exits immediately if:

  • Checker returns true → transition to Ready, persist status.
  • Job is no longer alive (killed/crashed) → stop polling.
  • 60-second timeout elapses → stop polling (job stays in Running).

Persistence

Jobs are persisted to disk under $XDG_DATA_DIR/bgrun/jobs/$ID/:

jobs/abc123/
├── meta.json     # Full JobRecord (cmd, name, workspace, pid, state, readiness, restart, pty, max_runtime, max_rss_mb, env)
├── status.json   # Current state, exit_code, ready_at, restart_count, cursor
└── stdout.log    # Captured stdout/stderr (rotated at 50MB → stdout.log.1)
  • meta.json is written once on spawn. Contains all configuration needed to re-create the job.
  • status.json is updated on state transitions. Restored on daemon restart.
  • stdout.log grows unbounded. Rotated at 50MB. The daemon writes with tokio::fs::File::create(true).append(true).

An audit log at $XDG_DATA_DIR/bgrun/audit.log records daemon startup timestamps in NDJSON format.


Resource monitoring

A global SYSINFO_SYSTEM static (once_cell::sync::Lazy<Arc<Mutex<System>>>) is initialized once in runner.rs. Both get_stats and the memory monitor share this single instance, avoiding per-call allocation:

#![allow(unused)]
fn main() {
let mut sys = SYSINFO_SYSTEM.lock().unwrap();
sys.refresh_processes(ProcessesToUpdate::All);
let proc = sys.process(Pid::from_u32(pid));
// proc.cpu_usage(), proc.memory(), proc.run_time()
}

Memory RSS guardrails

When --max-rss <MB> is passed to bgrun run, the daemon spawns monitor_memory_limit() — a tokio task that polls RSS every 1 second. If the process exceeds the limit, it is killed through the normal kill flow (SIGTERM → SIGKILL).

The max_rss_mb value is persisted in meta.json and restored on daemon restart, so memory limits survive reboots.


Tool schemas

bgrun schema <command> prints JSON Schema (draft-07) for any command’s argument struct using the schemars crate. The derive macros are on RunArgs, KillArgs, TailArgs, Command, ReadinessStrategy, and RestartPolicy in bgrun-proto.

This allows AI agents to discover expected input shapes at runtime without hardcoded tool definitions.


ID resolution

JobStore::resolve_id() accepts three formats:

  1. Full UUID — exact match against the job’s canonical ID.
  2. Job name — exact match against the name index (--name).
  3. Unique prefix — at least 4 characters matching exactly one job’s UUID.

This is called at the start of every daemon handler, so bgrun tail abc1, bgrun status my-server, and bgrun kill 55f3a all work transparently.


Log tail implementation

tail_lines uses a two-pass approach to avoid loading the entire file into memory:

  1. Pass 1: scan forward, tracking newline byte positions in a ring buffer of N+1 entries.
  2. Pass 2: seek to the start offset of the Nth-from-last line, read only that tail portion.

diff_since seeks directly to the cursor offset from status.json, counting lines from start for correct line numbering.

This works for log files of any size without O(file_size) memory usage.

bgrun.toml Reference

Place a bgrun.toml in your project’s git root (or any parent directory containing a .git directory). The CLI searches upward from cwd to find it.

This file defines named jobs with their commands, readiness checks, dependencies, and restart policies. Named jobs can be referenced from any CLI command instead of typing full command strings.

Complete example

[jobs.db]
cmd = "docker run --rm -p 5432:5432 postgres:16"
ready-when-port = 5432
workspace = "myapp"

[jobs.server]
cmd = "cargo run"
ready-when = "listening on"
workspace = "myapp"
after = "db"
restart = "on-crash"

[jobs.worker]
cmd = "python -m celery -A tasks worker"
ready-when = "celery@"
workspace = "myapp"
after = "db"
restart = "on-crash"

Fields

KeyTypeDescription
cmdstring (required)The shell command to run. Split on whitespace into argv.
ready-whenstringMark the job Ready when a log line contains this substring.
ready-when-portintegerMark the job Ready when localhost:<port> accepts TCP connections.
ready-when-urlstringMark the job Ready when GET <url> returns HTTP 2xx.
ready-when-filestringMark the job Ready when the given file path exists.
ready-when-regexstringMark the job Ready when a log line matches this regex.
ready-when-filestringMark the job Ready when the given file path exists.
restartstring"on-crash" — restart if the process exits with non-zero code or is killed by signal.
workspacestringGroup jobs for batch operations (bgrun list --workspace, bgrun kill --workspace).
afterstringName of another job that must reach Ready (or exit) before this one starts. 120s timeout.
ptyboolAllocate a pseudo-terminal for the child process.
max-rss-mbintegerKill the job if its RSS exceeds this value (MB).
max-runtime-msintegerKill the job after this many milliseconds.
backoff-msintegerBase backoff in ms for restart delay (default: 2000). Doubles each consecutive failure, capped at 5 min.
cwdstringWorking directory for the job.
envtableEnvironment variables, e.g. env = { FOO = "bar", BAZ = "qux" }.
allocate-portstringAllocate a free ephemeral TCP port and set it as this env var name (e.g. "PORT").
health-check-urlstringPoll this HTTP URL periodically after ready for liveness.
health-check-portintegerProbe this TCP port periodically after ready for liveness.
health-interval-secsintegerSeconds between health checks (default: 10).
health-thresholdintegerConsecutive failures before killing (default: 3).
colsintegerPTY width in columns (default: 80, only with pty = true).
rowsintegerPTY height in rows (default: 24, only with pty = true).

A maximum of one readiness strategy can be configured. If multiple are specified, the CLI picks the first one found in order: ready-when, ready-when-port, ready-when-url, ready-when-file.

Name resolution rules

  1. bgrun run, bgrun run-group, and all commands accepting --name check bgrun.toml first.
  2. If the name matches a [jobs.<name>] entry, the configured cmd, workspace, after, and readiness are used as defaults.
  3. CLI flags override config values. Example: bgrun run --name server "cargo run --release" uses the custom command but still inherits the config’s workspace and after.
  4. If a name is not found in bgrun.toml, it’s treated as a raw command to execute.

Run-group behavior

bgrun run-group spawns all named jobs in parallel but respects after dependencies:

bgrun run-group db server worker

Execution order:

  1. db starts immediately (no dependencies).
  2. server waits for db to become Ready or exit.
  3. worker waits for db to become Ready or exit.

server and worker may start in any order once db is resolved, since neither depends on the other.

Timeout: If a dependency doesn’t reach Ready or exit within 120 seconds, the dependent job fails with a timeout error.

Idempotency

Named jobs are idempotent: running bgrun run --name server when a server job is already alive returns the existing job record instead of spawning a duplicate. This is safe for scripts and agent loops that call run repeatedly.

To force a restart, kill the job first:

bgrun kill server
bgrun run --name server "cargo run"

Config discovery

The CLI walks from the current working directory up to the git root (first directory containing .git) looking for bgrun.toml. If found, it’s loaded automatically. No --config flag needed.

myproject/
├── .git/
├── bgrun.toml       ← found here
├── src/
│   └── main.rs
└── tests/
    └── ...

Command Reference

bgrun has 18 subcommands. All commands return JSON to stdout when piped, or human-readable output when connected to a terminal.

The daemon auto-starts on the first CLI invocation. You don’t need to manually start it.


run

Start a background process.

bgrun run [OPTIONS] <cmd> [args...]

Flags

FlagDescription
--name <NAME>Named job (enables idempotent re-run; second run --name X returns the existing job if alive)
--workspace <WS>Group jobs for batch operations
--ready-when <PATTERN>Mark job Ready when a log line matches this substring
--ready-when-regex <REGEX>Mark job Ready when a log line matches this regex
--ready-when-port <PORT>Mark job Ready when TCP port localhost:PORT accepts connections
--ready-when-url <URL>Mark job Ready when GET returns HTTP 2xx
--ready-when-file <PATH>Mark job Ready when file exists
--after <NAME>Wait for named job to reach Ready (or Exited/Crashed) before spawning
--ptyAllocate a pseudo-terminal (useful for processes that buffer output differently with pipes). The PTY is allocated by bgrun’s portable-pty library. Known limitation: programs that open their own PTY (e.g. podman exec -it, ssh, docker attach) may not work with --pty because the child’s PTY is consumed by the inner command rather than bgrun’s PTY master.
--restart on-crashAuto-restart if the process exits non-zero (SIGKILL, crash, non-zero exit)
--backoff <DURATION>Delay between restart attempts, e.g. 2s, 5m, 500ms (default: 2s, only with --restart)
--max-rss <MB>Kill the job if resident memory exceeds this threshold (checked every 1s)
--max-runtime <D>Kill the job after this duration (e.g. 30s, 5m)
--cols <N>PTY width in columns (default: 80, only with --pty)
--rows <N>PTY height in rows (default: 24, only with --pty)
--allocate-port <NAME>Allocate a free TCP port and set it as the environment variable <NAME>
--health-check-url <URL>Poll an HTTP URL periodically for liveness after ready
--health-check-port <PORT>Probe a TCP port periodically for liveness after ready
--health-interval <SECS>Seconds between health checks (default: 10)
--health-threshold <N>Consecutive health check failures before killing (default: 3)

Examples

# Simple background process
bgrun run "npm run dev"

# Named + readiness
bgrun run --name server --ready-when "listening on" "cargo run"

# With restart
bgrun run --name worker --restart on-crash --backoff 5s "python worker.py"

# Depends on another job
bgrun run --name tests --after server "cargo test"

# PTY allocation
bgrun run --pty "npm run dev"

Output (JSON)

{"id":"abc123","name":"server","workspace":null,"cmd":["cargo","run"],"pid":12345,"state":"running","started_at":"2026-05-31T00:00:00Z"}

If a named job is already running, the existing record is returned instead of spawning a duplicate.


run-group

Start multiple named jobs in parallel. Each name is resolved from bgrun.toml (see bgrun.toml Reference). Jobs respect their after dependencies — the group waits for each job’s dependencies before spawning it.

bgrun run-group <NAME> [NAME...]

Examples

# Start all jobs defined in bgrun.toml
bgrun run-group db server worker

# Start specific group
bgrun run-group server worker

Output (JSON)

[
  {"id":"aaa","name":"server","state":"running",...},
  {"id":"bbb","name":"worker","state":"running",...}
]

list

List all known jobs, or filter by workspace.

bgrun list [--workspace <WS>]

Examples

# All jobs
bgrun list

# Jobs in a specific workspace
bgrun list --workspace myapp

Output (JSON) — one JSON object per line (NDJSON):

{"id":"abc","name":"server","workspace":"myapp","cmd":["cargo","run"],"pid":12345,"state":"running","started_at":"..."}
{"id":"def","name":"worker","workspace":"myapp","cmd":["python","worker.py"],"pid":12346,"state":"exited","started_at":"..."}

status

Get the current state of a job.

bgrun status [<ID>] [--name <NAME>]

Flags

FlagDescription
--name / -n <NAME>Job name (alternative to positional ID)

Examples

bgrun status abc123
bgrun status server  # works with names too
bgrun status --name server

Output (JSON)

{"state":"running","exit_code":null,"ready_at":null,"restart_count":0,"last_diff_cursor":0}
FieldMeaning
stateStarting, Running, Ready, Exited, Crashed, or Killed
exit_codeProcess exit code (null while running)
ready_atRFC 3339 timestamp when readiness check passed
restart_countHow many times the process has been restarted
last_diff_cursorByte offset for incremental log tailing

tail

Show the last N lines of a job’s stdout/stderr log.

bgrun tail <ID> [--lines <N>] [--digest] [--level <LEVEL>] [--stream <S>] [--strip-ansi] [--follow] [--filter-regex <R>]

Flags

FlagDescription
--lines <N>Number of lines to show (default: 20)
--digestShow summary instead of raw lines (error/warn count, last error)
--level <LEVEL>Filter lines containing error or warn (case-insensitive)
--stream <S>Filter by stream source (stdout, stderr, pty)
--strip-ansiStrip ANSI escape codes from output
--followFollow new log lines in real time
--filter-regex <R>Filter log lines matching a regex pattern

Examples

# Last 10 lines
bgrun tail server --lines 10

# Digest summary
bgrun tail server --digest

# Show only lines containing "error"
bgrun tail server --level error

Output (JSON)

{
  "lines": [
    {"line_number": 42, "content": "listening on :8080", "timestamp": null},
    {"line_number": 43, "content": "GET / 200 OK", "timestamp": null}
  ]
}

Human output colorizes errors in red and warnings in yellow.


diff

Show log lines added since the last diff call (tracked via cursor).

bgrun diff <ID> [--lines <N>] [--stream <S>] [--strip-ansi] [--filter-regex <R>]

Flags

FlagDescription
--lines <N>Number of lines to show (unlimited if not set)
--stream <S>Filter by stream source (stdout, stderr, pty)
--strip-ansiStrip ANSI escape codes from output
--filter-regex <R>Filter log lines matching a regex pattern

Examples

# First call: all log content
bgrun diff server

# Later: only new lines since last call
bgrun diff server

Output (JSON)

{
  "cursor": 2048,
  "lines": [
    {"line_number": 100, "content": "some new output", "timestamp": null}
  ]
}

wait

Block until a job reaches Ready state, or a timeout elapses. If the job exits or crashes before becoming ready, returns immediately with the terminal state and exit code.

bgrun wait <ID> [--timeout <DURATION>]

Examples

# Wait up to 60s
bgrun wait server

# Wait up to 5 minutes
bgrun wait db --timeout 5m

Output (JSON)

{"ready":true,"elapsed_ms":1234,"exit_code":null,"state":null}

If the job exits with code 0 before becoming Ready (pattern not matched):

{"ready":false,"elapsed_ms":350,"exit_code":0,"state":"exited"}

If the job crashes before becoming Ready:

{"ready":false,"elapsed_ms":150,"exit_code":1,"state":"crashed"}

If the timeout elapses while the job is still Running:

{"ready":false,"elapsed_ms":60000,"exit_code":null,"state":null}

kill

Terminate a job by ID, name, or entire workspace.

bgrun kill [<ID>] [--name <NAME>] [--workspace <WS>]

Flags

FlagDescription
--name / -n <NAME>Job name (alternative to positional ID)
--workspace <WS>Kill all jobs in a workspace

Sends SIGTERM first, then SIGKILL after 5 seconds if the process hasn’t exited. Sends to the entire process group, so child processes are cleaned up.

Examples

# Kill by ID
bgrun kill abc123

# Kill by name
bgrun kill server
bgrun kill --name server

# Kill all jobs in a workspace
bgrun kill --workspace myapp

Output (JSON) — single kill:

{"killed":["abc123"]}

Workspace kill:

{"killed":["abc123","def456"]}

send

Write data to a job’s stdin.

bgrun send <ID> [<DATA>] [--newline] [--enter]

Flags

FlagDescription
--newlineAppend a newline to the data (deprecated: use --enter instead)
--enterSend just an Enter (newline), optionally with data

Examples

# Send text
bgrun send server "/reload"

# Append newline
bgrun send server "/reload" --newline

# Send just Enter (press Return)
bgrun send server --enter

# Send text followed by Enter
bgrun send server "yes" --enter

# Using shell escape
bgrun send server $'yes\n'

Output (JSON)

{"ok":true}

Works with both piped and --pty jobs.


stats

Show CPU and memory usage of a running process.

bgrun stats <ID>

Examples

bgrun stats server

Output (JSON)

{"cpu_pct":2.4,"rss_mb":48,"uptime_secs":3600}
FieldMeaning
cpu_pctCPU usage percentage (across all cores)
rss_mbResident memory in MB
uptime_secsProcess uptime in seconds

skill

Install the embedded skill bundle to a target directory.

bgrun skill install <DIR>

Examples

bgrun skill install ~/.config/opencode/skills/bgrun

Output

Installed skill to /home/user/.config/opencode/skills/bgrun/SKILL.md

attach

Attach to a PTY job’s interactive terminal. Enables raw bidirectional communication with a process running in a pseudo-terminal.

bgrun attach <ID>

Examples

bgrun attach server

Once attached, the terminal enters raw mode:

  • Keystrokes are forwarded to the PTY job’s stdin.
  • The job’s PTY output is displayed live.
  • Ctrl+C detaches without killing the job (unlike a normal terminal).
  • *Ctrl+* (SIGQUIT) is also forwarded but may terminate the job.
  • Terminal resize events are forwarded to the PTY master.

The connection is closed automatically when the job exits.


screen

Show last N lines from a job’s in-memory ring buffer (non-blocking). Unlike bgrun tail, this reads from a live memory buffer rather than disk, making it suitable for rapid polling without I/O overhead.

bgrun screen <ID> [--lines <N>]

Flags

FlagDescription
--lines <N>Number of lines to show (default: 20)

Examples

# Last 20 lines
bgrun screen server

# Last 5 lines
bgrun screen server --lines 5

Output (JSON)

["line 1", "line 2", "line 3"]

Human output prints each line directly.


clean

Remove all terminated (crashed/exited/killed) jobs.

bgrun clean [--workspace <WS>] [--force]

Flags

FlagDescription
--workspace <WS>Only clean jobs in this workspace
-f / --forceSkip confirmation prompt

Examples

# Remove all terminated jobs
bgrun clean

# Remove only terminated jobs in a workspace
bgrun clean --workspace myapp

# Skip confirmation
bgrun clean --force

Output (JSON)

{"removed":["abc123","def456"]}

expect

Wait for a pattern to appear in a job’s log output. Returns when the pattern is found or the timeout expires.

bgrun expect <ID> <PATTERN> [--regex] [--timeout <DURATION>]

Flags

FlagDescription
--regexTreat pattern as a regular expression
--timeout <D>Max wait time, e.g. 30s, 5m (default: 60s)

Examples

# Wait for substring
bgrun expect server "listening on"

# Wait for regex
bgrun expect server "http://localhost:\d+" --regex

# With custom timeout
bgrun expect server "ready" --timeout 10s

Output (JSON) — on match:

{"matched":true,"line_number":42,"content":"listening on :8080"}

On timeout:

{"matched":false,"line_number":null,"content":null}

schema

Print JSON Schema (draft-07) for a command’s argument struct. Designed for AI agents to discover the expected input shape at runtime.

bgrun schema <COMMAND>

Supported commands: run, kill, tail.

Examples

bgrun schema run
bgrun schema kill
bgrun schema tail

Output (JSON) — a standard JSON Schema document with title, type, properties, and required fields.


completions (hidden)

Hidden subcommand for shell autocomplete scripts. Prints tab-separated job information for shell tab-completion, or generates full completion scripts.

bgrun completions --active-ids
bgrun completions --workspaces
bgrun completions --shell fish
bgrun completions --shell bash
bgrun completions --shell zsh

Flags

FlagDescription
--active-idsPrint active short IDs with state descriptions
--workspacesPrint unique active workspaces
--shell <fish|bash|zsh>Generate a complete completion script for the given shell

Installation

# Fish
bgrun completions --shell fish > ~/.config/fish/completions/bgrun.fish

# Bash
bgrun completions --shell bash | sudo tee /etc/bash_completion.d/bgrun

# Zsh
bgrun completions --shell zsh > /usr/local/share/zsh/site-functions/_bgrun

Dynamic completion integration

The --active-ids and --workspaces flags produce live data from the daemon, used by shell functions:

# In ~/.config/fish/completions/bgrun.fish:
complete -c bgrun -n "__fish_seen_subcommand_from status kill wait tail diff send stats attach expect" -a "(bgrun completions --active-ids)"
complete -c bgrun -n "__fish_seen_subcommand_from list kill; and __fish_prev_arg_in --workspace" -a "(bgrun completions --workspaces)"

Interactive menu

Running bgrun without any subcommand opens an interactive TUI menu:

  • List & Refresh Jobs — runs bgrun list
  • View Job Status/Stats — select a job, shows status and resource stats
  • Attach to Interactive PTY — select a job and attach
  • Tail Job Logs — select a job, shows last 20 lines
  • Kill a Job — select a job and kill it
  • Exit Menu

The job list is populated live from the daemon, showing short ID, name, state, and command.


ID resolution

All commands that accept a job ID also accept:

  • Full UUID — the canonical job identifier
  • Job name — as set with --name
  • Unique prefix — at least 4 characters of the UUID that match exactly one job
bgrun status abc1          # prefix match
bgrun status my-server     # name match
bgrun status abc12345...   # full UUID

Job States

Jobs transition through these states:

Starting ──► Running ──► Ready
   │            │
   ▼            ▼
 Exited       Crashed    (non-zero exit / SIGKILL)
                  Killed  (explicit kill)

A job in any of Starting, Running, or Ready is considered “alive.” Exited, Crashed, and Killed are terminal states.