Run long shell commands (test suites, builds, linters) as detached background jobs so your
AI coding agent's session stays unblocked and its context stays clean. Output lands in a file
under .run/; the command returns immediately. State is file-based, so a new agent session can
pick up a job started by a prior one.
On OpenCode, a companion plugin wakes the originating agent session when a job finishes — so the agent proactively reads results and reacts, rather than waiting for a human to notice a notification.
./install.sh # symlink scripts + plugin + skill
# restart OpenCode # required for the plugin to loadAs an AI agent (OpenCode) — call the bgrun tool:
bgrun(command: "make test-short")
// returns immediately: "started: <job-id>"
// agent is woken automatically when the job finishes
As a human in a shell — call the script directly:
bgrun make test-short
# returns immediately; desktop notification fires on completionAI agents on OpenCode call the bgrun tool — a first-class OpenCode plugin tool.
The tool captures context.sessionID in-process and forwards it to the underlying shell
script via a -s <session-id> flag. When the job finishes, the plugin wakes that exact
session via client.session.promptAsync(). This is correct even when many concurrent agent
sessions share the same directory.
Humans in a shell call the bgrun script directly. A human shell invocation has no live
agent session to wake, so it is notify-only — a desktop notification fires via
terminal-notifier (or osascript as fallback) when the job finishes. This is expected and
honest behavior. Omitting -s always produces notify-only behavior.
The bgrun script writes sidecar files into <project-root>/.run/ for each job:
| Sidecar | Contents |
|---|---|
.status |
Key=value state: state, pid, exit, cmd, … |
.log |
Captured command output |
.origin |
Which tool launched the job (opencode | claude | cursor; defaults to opencode) |
.session |
Session id to wake (written only when -s is passed) |
The .session sidecar is written atomically before the job is spawned, so even a job that
finishes instantly is routed correctly — there is no race.
On completion the runner drops a .notify breadcrumb. The plugin polls .run/ (~1 s interval),
atomically claims each .notify by renaming it to .notified (fire-once — survives plugin
restarts), reads the .session sidecar for exact routing, and wakes that session. If no
.session sidecar is present the plugin falls back to the most-recently-active session.
Non-fatal degradation: if the wake fails for any reason — plugin not loaded, SDK missing, etc. — the job still completes normally and the human desktop notification fires as usual.
These are registered by the plugin as first-class OpenCode tools. Agents call them directly — no shell access required.
| Tool | Purpose |
|---|---|
bgrun |
Launch a command as a detached background job. Returns started: <job-id> immediately. Injects sessionID automatically so the plugin can wake this exact session on completion. |
bgclean |
Delete completed job artifacts. Default: 7 days. Skips running jobs. The plugin also auto-cleans on startup (14-day default). |
Plain shell scripts — notify-only, no session wake. Available on PATH after npm i -g or install.sh.
| Command | Purpose |
|---|---|
bgrun [--name <label>] <command…> |
Launch a detached background job; desktop notification fires on completion (no session wake). --name sets a human-readable label used as the job slug. |
bgstatus [--json] [<job-id>] |
Show status (running, done, crashed) and exit code; omit job-id to list all jobs. --json emits a machine-readable JSON array (fields: id, state, cmd, pid, start, end, exit, cwd, name, log_path). |
bgtail <job-id> [lines] |
Print the last N lines of the job's log (default 40). |
bgwait <job-id> |
Block until the job finishes; exits with the job's exit code. Exit 1 if job not found; exit 2 on timeout (1 hour). Useful for sequential shell scripting. |
bgkill <job-id> |
Send SIGTERM to a running job and mark it killed. Exits 1 if the job is not running or not found. |
bgclean [days] |
Delete job artifacts older than N days (default: 7). |
bgrun doctor |
Health check: verifies BGRUN_DIR is writable, _bgrunner.sh is present, notification tools (osascript/notify-send) are available, and OpenCode is detected. Exits 0 if all required checks pass, 1 otherwise. |
Auto-cleanup: the plugin silently removes job artifacts older than 14 days each time
OpenCode starts. Explicit bgclean calls default to 7 days — more aggressive, since you're
asking for it. Both thresholds can be overridden by passing a days argument.
All environment variables are optional. Defaults work out of the box for typical single-user installs.
| Variable | Default | Scope | Description |
|---|---|---|---|
BGRUN_DIR |
~/.bgrun/jobs |
Plugin + all shell CLIs | Override the directory where job artifacts (.status, .log, .notify, .session) are stored. Useful for CI, multi-user machines, or when you want jobs on a different volume. |
BGRUN_ACTIVATE |
(none) | Shell CLI only | macOS bundle ID of the app to activate when a desktop notification is clicked (e.g. com.todesktop.230313mzl4w4u92). Has no effect without terminal-notifier. See desktop notifications for usage example. |
OPENCODE_CONFIG_DIR |
~/.config/opencode |
Plugin only | Override the OpenCode config directory. Used as a fallback path to locate the @opencode-ai/plugin SDK when bare import fails. Rarely needed — only relevant when OpenCode is installed to a non-default config location. |
Legacy .run/ migration: If the plugin detects a legacy .run/ directory with existing jobs in your project root, it prints a one-time informational message on startup suggesting you set BGRUN_DIR=./.run to preserve that layout, or let new jobs accumulate in the default ~/.bgrun/jobs. This message is not an error — no action is required; jobs in the old directory continue to be readable by the shell CLIs if you point BGRUN_DIR accordingly.
Add the following entry to the plugins array in your opencode.json:
{
"plugins": [
"@stablekernel/opencode-bgrun@0.1.2"
]
}OpenCode installs the package into
~/.cache/opencode/packages/@stablekernel/opencode-bgrun@<version>/node_modules/@stablekernel/opencode-bgrun/
and loads the plugin automatically on startup. No PATH setup is needed for the agent (tool)
path — the plugin resolves bin/bgrun by absolute path at runtime.
./install.shCreates symlinks:
| Source | Destination |
|---|---|
bin/bgrun, bin/bgstatus, bin/bgtail, bin/bgclean |
~/.local/bin/ |
plugin/bgrun-wake.js |
~/.config/opencode/plugin/ |
skill/run-bg/ |
~/.config/opencode/skills/run-bg/ |
./uninstall.sh removes those symlinks.
Restart OpenCode after install. OpenCode discovers plugins in ~/.config/opencode/plugin/
at startup — no opencode.json entry is needed — but the plugin is not hot-loaded; a restart
is required for the tool registration and completion poller to become active.
The bgrun tool (plugin-registered) is what AI agents call and it works automatically
from the npm install — no PATH setup required. It also delivers the session-wake feature.
The human shell commands (bgrun, bgstatus, bgtail, bgclean) are optional and
notify-only — they fire a desktop notification on completion but do not wake an agent
session. (Agent-wake is exclusively the plugin bgrun tool.) After an npm install via
opencode.json those scripts are not on your PATH; typing bgrun in a terminal will
produce command not found.
Recommended: install via npm global:
npm i -g @stablekernel/opencode-bgrunThis puts bgrun, bgstatus, bgtail, and bgclean on your PATH automatically via npm's
bin map.
Alternatively, if you have the repo cloned, run ./install.sh --cli-only to symlink just
the four CLI scripts into ~/.local/bin without touching the plugin or skill (which the npm
install already provides):
# If you have the repo cloned:
./install.sh --cli-only
# See all modes:
./install.sh --help--cli-only also works without a clone when the plugin is already installed via the
opencode.json npm spec. It auto-discovers the scripts inside OpenCode's package cache at:
~/.cache/opencode/packages/@stablekernel/opencode-bgrun@<version>/node_modules/@stablekernel/opencode-bgrun/bin/
The version segment in the path is discovered dynamically, so it keeps working across version
bumps. To use this mode you need the install.sh script itself — grab it from the repo or
copy it from the cache dir above.
To remove: ./uninstall.sh --cli-only.
If you prefer, symlink directly from the npm global cache:
PKG="$HOME/.cache/opencode/packages/@stablekernel/opencode-bgrun@0.1.2/node_modules/@stablekernel/opencode-bgrun"
mkdir -p "$HOME/.local/bin"
for cmd in bgrun bgstatus bgtail bgclean; do
ln -sf "$PKG/bin/$cmd" "$HOME/.local/bin/$cmd"
done
# Ensure ~/.local/bin is on PATH (add to ~/.zshrc or ~/.bashrc if needed):
# export PATH="$HOME/.local/bin:$PATH"The plugin loads the OpenCode SDK (@opencode-ai/plugin) via a resolution cascade: bare import
first, then the OpenCode config directory. If the SDK cannot be found the poller still runs
(desktop notifications work), but the bgrun tool and agent-wake require a successful SDK load.
macOS: Install terminal-notifier for richer notifications with click-through support:
brew install terminal-notifierTo get click-through to your editor when a notification fires, set BGRUN_ACTIVATE to your
editor's bundle ID in your shell profile:
# ~/.zshrc
export BGRUN_ACTIVATE=com.example.YourEditorLinux: notify-send is automatically detected and used if available — no setup required.
Run bgrun doctor to confirm it is found.
The live-agent wake is OpenCode-specific. Only an in-process plugin can call
client.session.promptAsync() to wake a live OpenCode session — a CLI path and an HTTP bridge
were both proven dead-ends. This component cannot be ported without a per-tool equivalent.
The file-based job runner (bin/) is tool-agnostic: plain POSIX sh, no OpenCode dependency.
The .origin sidecar is the intended routing seam for per-tool plugins — a Cursor or Claude
Code integration would read .origin and apply its own wake mechanism. These are separate
future deliverables; the core runner works today regardless of which agent (or no agent) is
present.
Summary: OpenCode-first for the full agent-wake experience; core job runner is portable.
bin/
bgrun # Launch a detached job; writes .run/ sidecars; bgrun doctor for health check
_bgrunner.sh # Detached executor (invoked by bgrun, not directly)
bgstatus # Check job status; --json for machine-readable output
bgtail # Tail job log output
bgwait # Block until a job finishes; exit with its exit code
bgkill # Send SIGTERM to a running job
bgclean # Remove old .run/ artifacts
plugin/
bgrun-wake.js # OpenCode plugin: registers bgrun tool + runs completion poller
skill/
run-bg/ # OpenCode skill: teaches agents when/how to use bgrun
install.sh # Symlink everything into place
uninstall.sh # Remove symlinks