Skip to content

Running Bullpen CLI headlessly (24/7 bots)

Bullpen CLI's auth flow is RFC 8628 device-auth — it requires an interactive browser on first login. For 24/7 bots that don't have a browser, the CLI can use a portable auth-set workflow only when doctor deploy-auth reports that the copied credential profile is supported: check bullpen status and bullpen doctor auth --refresh, run bullpen login on a workstation only when status or refreshed auth diagnostics say login is required, copy credentials.json.enc, credential_salt.bin, and keys/ as one set to your bot host, use the same absolute BULLPEN_HOME, use the same BULLPEN_USERNAME_OVERRIDE, keep the Bullpen home writable, and require a green doctor deploy-auth check before unattended operation. The copied bot profile can refresh while the refresh token remains valid and credential saves succeed, but there is no guaranteed 30-day unattended window.

If bullpen doctor deploy-auth --output json reports portability.supported_deployment_profile_available=false, treat the copied bundle as unsupported for unattended deployment and contact Bullpen support instead of assuming the bot host can decrypt or refresh it reliably. A scoped service-account/headless auth surface is still planned.

This page is about running normal CLI commands unattended with portable credentials. The separate bot runtime and TUI operator controls are preview features behind the agent-preview build feature, which is off in the default official release artifact.

Use one stable credential directory on both machines:

export BULLPEN_BOT_HOME=/var/lib/bullpen-bot

The examples below use /var/lib/bullpen-bot. If that path is not writable on your workstation, choose another persistent absolute path and use that exact same value everywhere on both hosts.

Keep local/dev binaries isolated from your official release profile. When running a locally built or development binary, set a separate absolute BULLPEN_HOME such as /var/lib/bullpen-bot-dev or another dedicated test home. Do not let local/dev runs write the default ~/.bullpen profile unless you are intentionally testing against that official credential home.


What gets copied

~/.bullpen/credentials.json.enc — the encrypted CLI credential file. It is portable only as part of a complete auth set for a supported deployment profile. Copy it together with the matching credential_salt.bin and keys/ directory, then verify the result with bullpen doctor deploy-auth --output json before unattended use.

For the file to work on a different host, use the same stable identity and absolute Bullpen home path on both machines. Two knobs control those deployment profile values:

  • BULLPEN_USERNAME_OVERRIDE — overrides the OS username in the key deployment profile. Set this to a stable identifier (e.g. botops) on both the workstation and the bot host.
  • BULLPEN_HOME — overrides the default ~/.bullpen directory. Set this to the same path on both hosts (e.g. /var/lib/bullpen-bot).

Use an absolute BULLPEN_HOME for bots, containers, cron, systemd, and CI. Relative values are rejected because they can change under supervisors and create a different Bullpen profile than you intended. The supported headless deployment targets for the credential-locking guarantees are macOS, Linux, and Windows through WSL2. Native Windows PowerShell or Command Prompt is not the supported unattended auth path yet.

Keep this directory writable for the running bot user. Current builds fail closed if a refreshed session cannot be saved durably, so the command that encounters the storage problem exits with a typed local-auth error instead of letting a later bot process discover stale credentials.

First-time local Turnkey keypair creation is serialized on supported Unix-style filesystems so concurrent bot startup processes share the same persisted keypair. Do not place BULLPEN_HOME on a cloud-sync or network filesystem unless you have validated locking and rename durability for that environment.

Without these overrides, the credential profile is tied to the workstation environment and may fail on the bot host.


Setup

1. Workstation (interactive login)

Not copy-pasteable as a login sequence: start on the workstation with the deployment profile and passive diagnostics.

export BULLPEN_USERNAME_OVERRIDE=botops
export BULLPEN_BOT_HOME=/var/lib/bullpen-bot
export BULLPEN_HOME="$BULLPEN_BOT_HOME"
mkdir -p "$BULLPEN_HOME"
chmod 700 "$BULLPEN_HOME"
bullpen status --output json                      # inspect local session state first
bullpen doctor auth --refresh --output json       # attempt refresh and confirm whether login is required
bullpen doctor deploy-auth --output json          # confirm supported deployment profile

Run bullpen login on the workstation only when status or refreshed auth diagnostics report that login is required. After login, re-run bullpen doctor auth --refresh to confirm Token and Refresh Token are valid, then re-run bullpen doctor deploy-auth --output json.

2. Bot host (headless)

This is a non-runnable sequence: copy the credential files only for the same supported deployment profile, then run the refresh diagnostic to verify the copied profile.

# Match the workstation's overrides exactly.
export BULLPEN_USERNAME_OVERRIDE=botops
export BULLPEN_BOT_HOME=/var/lib/bullpen-bot
export BULLPEN_HOME="$BULLPEN_BOT_HOME"
mkdir -p "$BULLPEN_HOME"
chmod 700 "$BULLPEN_HOME"

# Copy the encrypted credential file, its salt, and the Turnkey keypair over.
# If you changed BULLPEN_BOT_HOME, use that same path in these source paths.
scp workstation:/var/lib/bullpen-bot/credentials.json.enc "$BULLPEN_HOME/"
scp workstation:/var/lib/bullpen-bot/credential_salt.bin "$BULLPEN_HOME/"
scp -r workstation:/var/lib/bullpen-bot/keys "$BULLPEN_HOME/"

# Verify decryption + token health.
bullpen status --output json
bullpen doctor auth --refresh --output json
bullpen doctor deploy-auth --output json

If refreshed auth diagnostics show Token: Valid and Refresh Token: expires in N days, and doctor deploy-auth --output json reports portability.supported_deployment_profile_available=true, the copied bundle is decryptable and usable for this deployment profile. Continue monitoring the refresh-token lifetime as described below.

In the deploy-auth JSON, also inspect these fields:

  • home.absolute must be true.
  • home.canonical_path should match the intended Bullpen home.
  • home.writable must be true; home.writability_check explains that this is derived from filesystem metadata, not from a write probe.
  • home.filesystem_check is conservative. Current builds do not prove NFS, cloud-sync, Docker volume, or host lock semantics here; use a local persistent disk for unattended bots unless your deployment validates locking and rename durability separately.

3. Refresh-token rotation

The bot's refresh token has a ~30-day TTL. Authenticated commands refresh the access token as needed. A refresh response must contain both a non-empty access_token and a non-empty refresh_token. The CLI persists the returned pair only after the refreshed credentials are durably saved and read-back verified.

If the response omits either token or returns either token empty, the refresh fails closed as AUTH_REFRESH_PERSISTENCE_FAILED. The CLI does not advance the in-process bearer token and leaves the old on-disk credentials.json.enc credentials untouched. Treat that as a storage/service diagnostic case: keep the old files for inspection, run bullpen status --output json and bullpen doctor auth --refresh --output json, and do not start a new login unless diagnostics say login is required.

For unattended bots, keep credentials.json.enc writable and run at least one authenticated command regularly so a valid server-provided rotated token can be saved. Use refreshed auth diagnostics to monitor the remaining refresh-token lifetime and re-login before expiry if status or refreshed auth diagnostics say login is required.

Not copy-pasteable as an automation sequence: check days remaining only when you need a human-readable refresh-token TTL check.

bullpen doctor auth | grep "Refresh Token"
# → Refresh Token:     expires in 27 days

If the refresh token does expire, repeat the workstation checks, run bullpen login interactively only when status or refreshed auth diagnostics say login is required, and re-copy the full encrypted auth set: credentials.json.enc, credential_salt.bin, and keys/.


Security tradeoffs

The portable credential file gives the bot host full account power — it can place trades, withdraw funds (within the existing CLI's restrictions), and access the same surface as the human operator.

  • Lock down file permissions: the credential file is 0o600 by default. credential_salt.bin and keys/ are also part of the portable auth set. Make sure BULLPEN_HOME is on a filesystem only readable by the bot's OS user.
  • Don't share BULLPEN_USERNAME_OVERRIDE-bound files between distinct bots. Each independent bot identity should run a separate bullpen login cycle with its own override identifier.
  • Don't commit the credential file to git. Treat the whole portable auth set as account credentials. Anyone who obtains a working copied set and its deployment profile can act as the account.
  • Revoke first if a portable auth set leaks. Run normal bullpen logout from every host/profile where the copied credentials still work so the server-side session/token is revoked before local files are cleared. Then use a new BULLPEN_USERNAME_OVERRIDE, log in again only after diagnostics require it, and re-copy a fresh complete auth set. Override rotation alone is not a sufficient leak response.

A scoped service-account/headless auth surface is planned so you can mint scoped, revocable credentials from a workstation and use them headlessly without copying any files. Until then, the portable credential workflow is the only supported headless workaround. Use it only on a host you control, with a dedicated OS user and locked-down filesystem permissions.


Troubleshooting

  • Bundle: NOT decryptable — keypair mismatch detected → the BULLPEN_USERNAME_OVERRIDE or BULLPEN_HOME differs between the workstation and the bot host. Double-check both env vars and use an absolute BULLPEN_HOME on both machines.
  • Token: expired; refresh token expired; run: bullpen login → the refresh token TTL elapsed. Re-run the workstation login + re-copy. If diagnostics mention credentials.json.enc or credential_salt.bin, preserve bullpen doctor auth --output json first and do not reset local auth state until diagnostics or support confirm it is unrecoverable.
  • bullpen doctor auth reports Credentials: Not found → the credential file didn't make it across, or BULLPEN_HOME is pointing somewhere else.

End.