steer

Proc

Background processes that start ready and never zombie.

Skills that need a dev server or long-running helper all duplicate the same ~250 lines of fragile lifecycle bash: spawn with nohup, poll a port, write a PID file, grep logs for "started", remember TERM-then-KILL, work around process reapers. Across the skills we analyzed, this was the buggiest code they carried.

steer proc owns that lifecycle. Bookkeeping lives in <workspace>/.steer/proc/<name>/: meta.json (pid, command, readiness) and the captured log. start --cwd <dir> sets the working directory.

When to use it

  • The skill starts anything that outlives a single command: a dev server, a preview, a worker that later steps talk to.
  • Anthropic's webapp-testing ships scripts/with_server.py, 105 lines of exactly this lifecycle, and it shows where hand-rolls crack: it terminates the process it spawned rather than the group (a wrapper like npm run dev dies, the server it started survives), and it pipes server output to a buffer nobody drains (a chatty server stalls mid-startup). steer proc logs to a file and stops the whole group.
  • Skip it for commands that finish on their own. proc is for the things you would otherwise track with a PID file.

CLI

steer proc start web --ready-port 5173 -- npm run dev
steer proc start api --ready-log "Listening on" --timeout 60 -- ./server
steer proc status [web] [--json]
steer proc logs web -n 100
steer proc stop web

Everything after -- is the command. Readiness:

  • --ready-port N: wait until the port accepts connections
  • --ready-log TEXT: wait until the text appears in the captured log
  • neither: steer checks that the process survives its first half-second

If the process exits immediately or misses the readiness deadline, steer stops it and reports the log tail, so the agent sees why instead of a silent zombie.

✓ Started 'web' (pid 73901) (port 5173 open)
  log: /work/acme/.steer/proc/web/log
  stop with: steer proc stop web

Guarantees

  • Detached: processes run in their own session and survive the shell that started them.
  • Stop kills the group: npm run dev → node children die too; TERM → wait → KILL escalation.
  • No double starts: starting a running name is refused with the existing pid.
  • Recycled-PID guard: status compares the running command against what was started; a reused pid reads as stopped, and steer only ever signals processes it started.
  • Logs captured: stdout+stderr land in the log file; steer proc logs tails it.

Python

from steer.proc import start, status, logs, stop, ProcError

info = start("web", ["npm", "run", "dev"], workspace=".", ready_port=5173)
try:
    ...
finally:
    stop("web", workspace=".")

In a SKILL.md

steer new --with proc generates:

Start helpers through the bundled runtime so nothing leaks or zombies:

    python3 scripts/steer.py proc start dev --ready-port 5173 -- npm run dev
    python3 scripts/steer.py proc status dev
    python3 scripts/steer.py proc stop dev      # always stop what you started

On this page