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:
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~/.bullpendirectory. 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.absolutemust betrue.home.canonical_pathshould match the intended Bullpen home.home.writablemust betrue;home.writability_checkexplains that this is derived from filesystem metadata, not from a write probe.home.filesystem_checkis 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.
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
0o600by default.credential_salt.binandkeys/are also part of the portable auth set. Make sureBULLPEN_HOMEis 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 separatebullpen logincycle 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 logoutfrom 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 newBULLPEN_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→ theBULLPEN_USERNAME_OVERRIDEorBULLPEN_HOMEdiffers between the workstation and the bot host. Double-check both env vars and use an absoluteBULLPEN_HOMEon both machines.Token: expired; refresh token expired; run: bullpen login→ the refresh token TTL elapsed. Re-run the workstation login + re-copy. If diagnostics mentioncredentials.json.encorcredential_salt.bin, preservebullpen doctor auth --output jsonfirst and do not reset local auth state until diagnostics or support confirm it is unrecoverable.bullpen doctor authreportsCredentials: Not found→ the credential file didn't make it across, orBULLPEN_HOMEis pointing somewhere else.
End.