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: log in once on a workstation, 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.


What gets copied

~/.bullpen/credentials.json.enc — AES-256-GCM-encrypted. The encryption key is derived from a stable embedded secret shared across official CLI builds, the OS username, the BULLPEN_HOME path, and the per-installation salt stored in credential_salt.bin.

For the file to decrypt on a different host, the username and home path components of the key derivation must match, and the matching credential_salt.bin must be copied with the encrypted files. Two knobs control the username and path components:

  • BULLPEN_USERNAME_OVERRIDE — overrides the OS username in the key derivation. 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 file is bound to your workstation's OS user and home dir and will fail to decrypt on the bot host.


Setup

1. Workstation (interactive login)

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 login                                     # opens browser, completes device-auth
bullpen doctor auth                               # confirm Token + Refresh Token both Valid
bullpen doctor deploy-auth --output json          # confirm supported deployment profile

2. Bot host (headless)

# 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 doctor auth
bullpen doctor deploy-auth --output json

If doctor auth shows 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. During refresh, the server may rotate the refresh token by returning a protobuf refresh_token response field; when present, the CLI persists that rotated value. When the response field is empty, the CLI keeps the existing stored refresh token.

That means rotation is not guaranteed on every refresh. For unattended bots, keep credentials.json.enc writable and run at least one authenticated command regularly so any server-provided rotated token can be saved. Use doctor auth to monitor the remaining refresh-token lifetime and re-login before expiry if the expiry is not extending. The source of truth is crates/bullpen-core/src/auth/token_session.rs::refresh_access_token.

Check days remaining at any time:

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

If the refresh token does expire, repeat the workstation step (run bullpen login interactively) 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. It's encrypted, but the embedded secret is shared across all CLI users — anyone who can also guess your username override + bullpen home can decrypt it.
  • Rotate the override + re-login if you suspect the file has leaked.

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 recommended path.


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.