Skip to content

Repository files navigation

opencode-bgrun

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.


Quick start

./install.sh        # symlink scripts + plugin + skill
# restart OpenCode  # required for the plugin to load

As 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 completion

How it works

Two audiences

AI 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.

Wake routing

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.


Commands

Plugin tools (agent-facing)

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).

Shell CLIs (human-facing)

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.


Configuration

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.


Install details

Recommended: npm install via opencode.json (coworkers / CI)

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.

Local / dev install (repo clone)

./install.sh

Creates 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.

Optional: human shell CLI

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.sh --cli-only

Recommended: install via npm global:

npm i -g @stablekernel/opencode-bgrun

This 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.

Manual alternative

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"

Plugin SDK resolution

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.

Optional: desktop notifications

macOS: Install terminal-notifier for richer notifications with click-through support:

brew install terminal-notifier

To 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.YourEditor

Linux: notify-send is automatically detected and used if available — no setup required. Run bgrun doctor to confirm it is found.


Scope and portability

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.


Components

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages