Agents¶
VibePod manages each agent as a Docker container. Credentials and config are persisted to ~/.config/vibepod/agents/<agent>/ on your host and mounted into the container on every run, so you only need to authenticate once.
Supported Agents¶
| Agent | Provider | Shortcut | Image |
|---|---|---|---|
claude |
Anthropic | vp c |
vibepod/claude:latest |
gemini |
vp g |
vibepod/gemini:latest |
|
opencode |
OpenAI | vp o |
vibepod/opencode:latest |
devstral (alias: vibe) |
Mistral | vp d |
vibepod/devstral:latest |
auggie |
Augment Code | vp a |
vibepod/auggie:latest |
copilot |
GitHub | vp p |
vibepod/copilot:latest |
codex |
OpenAI | vp x |
vibepod/codex:latest |
pi |
Earendil | vp pi |
vibepod/pi:latest |
Alias note: vp run vibe resolves to vp run devstral.
First run & authentication¶
Start any agent for the first time with vp run <agent>. The container will prompt you to authenticate (browser OAuth, API key entry, or device flow depending on the provider). Once authenticated, credentials are written to the persisted config directory and reused on subsequent runs.
Auto-pulling the latest image¶
VibePod automatically pulls the latest image for an agent before every run. This ensures you always start with the most up-to-date container without manual intervention.
To disable auto-pull globally:
Or for a specific agent only:
Per-agent auto_pull takes precedence over the global setting. For example, you can disable it globally but keep it on for a specific agent:
auto_pull: false # skip pull by default
agents:
claude:
auto_pull: true # except claude — always pull
You can also force a one-off pull via the CLI flag regardless of config:
The resolution order is: --pull flag > per-agent auto_pull > global auto_pull.
Overriding the image¶
You can point VibePod at a custom image via an environment variable:
Or permanently via your global config:
Image customization workflows¶
VibePod has a fixed set of supported agent IDs (claude, gemini, opencode, devstral, auggie, copilot, codex, pi). The CLI also supports the alias vibe, which resolves to devstral. Image customization means changing the image used for one of those IDs.
1. Extend an existing image for an agent¶
Example: add tools to the default Claude image.
- Create a Dockerfile that extends the current base image.
# Dockerfile.claude
FROM vibepod/claude:latest
# Add project-specific utilities.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ripgrep jq \
&& rm -rf /var/lib/apt/lists/*
- Build and tag the derived image.
- Run with the new image (one-off) or set it in config (persistent).
# ~/.config/vibepod/config.yaml (or .vibepod/config.yaml)
agents:
claude:
image: myorg/claude-container:with-tools
2. Add a new image for an agent¶
Example: point opencode at a newly published internal image.
- Build/publish your image to a registry (for example
registry.example.com/team/opencode:2026-03-01). - Attach that image to the target agent in config.
- Start the agent.
You can also test quickly without editing config:
Passing environment variables¶
Use -e / --env to inject variables at runtime:
Persistent per-agent env vars can also be set in config:
Passing arguments to the agent¶
Any extra arguments after the agent name are appended to the agent command inside the container:
Use -- before agent flags so VibePod does not parse them as its own options:
For concrete syntax, check the agent's own CLI help. For example, Claude and Codex both accept model flags, but their exact flag names and values differ.
Init scripts before startup¶
Use agents.<agent>.init to run shell commands in the container before the agent launches. This is useful for installing extra tools in a custom image workflow.
The init commands run on every vp run for that agent and must be idempotent.
IKWID mode (--ikwid)¶
Use --ikwid to enable each agent's built-in auto-approval / permission-skip mode when supported.
| Agent | --ikwid appended args |
|---|---|
claude |
--dangerously-skip-permissions |
gemini |
--approval-mode=yolo |
devstral |
--auto-approve |
copilot |
--yolo |
codex |
--dangerously-bypass-approvals-and-sandbox |
opencode |
Not supported |
auggie |
Not supported |
pi |
Not supported |
Example:
Detached mode¶
Use -d / --detach to start an agent container in the background without attaching your terminal. The agent process starts immediately inside the container — -d only controls whether VibePod attaches your terminal to it.
Basic usage¶
The command prints the container name and returns immediately. You can also find it later with:
Interacting with a detached container¶
The agent is already running inside the container. You can exec into it to inspect state, install extra tools, or interact with the agent alongside its running process:
Use the container name printed by vp run -d or shown in vp list.
Tip
If you need to run setup commands before the agent launches, use agents.<agent>.init or extend the base image instead — these run inside the container before the agent process starts.
Managing detached containers¶
Check running agents:
vp list # shows running + configured agents
vp list --running # shows only running agents
vp list --json # machine-readable output
Stop a specific agent, a single container, or all agents:
vp stop claude # stop every container for the `claude` agent
vp stop vibepod-claude-a1b2c3d4 # stop one specific container (from `vp list`)
vp stop claude -f # force stop immediately
vp stop --all # stop every VibePod container
The argument is resolved as an agent name/shortcut first; anything else is looked up as a container name or ID. Only VibePod-managed containers can be stopped this way.
Caveats¶
auto_remove(default:true) — By default, containers are automatically removed when they stop. This means you cannot restart a stopped detached container; you need tovp runagain. Setauto_remove: falsein your configuration if you want stopped containers to persist.- Session logging — Sessions started with
--detachare not recorded in the VibePod session log since VibePod does not capture the interactive I/O. If you need session logging, run without--detach.
Reattaching a terminal¶
Closing the terminal window that runs vp run does not stop the container — the agent keeps running in the background under Docker. This is by design: the container's lifecycle is tied to Docker, not to your shell. Use it as a feature when you want to keep a long-running session alive across terminal restarts.
To rejoin a running container:
If exactly one managed container is running you can omit the name:
vp attach only works for containers that are already running and managed by VibePod. When you are done, close the terminal to leave it running, or stop it explicitly with vp stop <container>, vp stop <agent>, or vp stop --all.
Connecting to a Docker Compose network¶
When your workspace contains a docker-compose.yml or compose.yml, VibePod detects it and offers to connect the agent container to an existing network so it can reach your running services.
You can also specify the network explicitly:
Individual agents¶
Claude (Anthropic)¶
Credentials are stored in ~/.config/vibepod/agents/claude/. On first run, Claude's interactive setup will guide you through API key configuration.
Long-lived token (recommended)¶
Claude Code has a known upstream bug where OAuth access tokens (~8 h TTL) are not automatically refreshed from disk, forcing users to run /login roughly once per day. See Why this workaround exists below for the full bug history and links.
VibePod works around this by storing a ~1-year long-lived token on the host and injecting it as CLAUDE_CODE_OAUTH_TOKEN on every run. This sidesteps the refresh path entirely.
This is an official authentication method
claude setup-token and the CLAUDE_CODE_OAUTH_TOKEN environment variable are both documented by Anthropic as a supported authentication path for CI pipelines, scripts, and other environments where an interactive browser login isn't available. See the official Claude Code authentication docs and the claude-code-action setup guide. VibePod just automates the storage and injection.
One-time setup:
This starts the container with claude setup-token, which opens Anthropic's OAuth flow in your browser. After you authorise, the container prints a token. VibePod then prompts you to paste it and saves it to:
Subsequent runs:
VibePod detects the stored token and injects CLAUDE_CODE_OAUTH_TOKEN automatically. Look for Using stored Claude OAuth token in the startup output to confirm.
Precedence (first match wins):
-e ANTHROPIC_API_KEY=...or-e CLAUDE_CODE_OAUTH_TOKEN=...passed on the CLIANTHROPIC_API_KEYorCLAUDE_CODE_OAUTH_TOKENset in your per-agentenv:config- Stored
oauth-tokenfile - Interactive OAuth via
.credentials.json(subject to the refresh bug)
Verifying the token is stored:
Shows credentials state, stored-token presence and mtime, and which auth mode the next run will use. You can also inspect the file directly:
ls -l ~/.config/vibepod/agents/claude/oauth-token
# or to view contents (treat as a secret — do not share):
nano ~/.config/vibepod/agents/claude/oauth-token
Verifying the token works:
-p runs Claude Code in headless mode — one API call, one response. If you see "ok", the token is valid.
Caveats:
- The long-lived token is inference-only — it cannot establish Remote Control sessions (steering a container from claude.ai/code or the mobile app).
claude setup-tokenrequires a Pro, Max, Team, or Enterprise plan. Console (pay-per-token) accounts should useANTHROPIC_API_KEYinstead.- The token rotates roughly once a year. When it expires, just run
vp run claude setup-tokenagain.
Using an API key instead¶
If you're on a Console (pay-per-token) account, set ANTHROPIC_API_KEY and skip the setup-token flow entirely:
Or permanently in config:
Diagnostics¶
vp doctor claude is the first tool to reach for when auth misbehaves. It reports:
.credentials.json— file owner/mode,expiresAt, presence ofrefreshToken, scopes, subscription type.claude.json— mtime cross-check- Stored long-lived token state
- Which host env vars (
ANTHROPIC_API_KEY,CLAUDE_CODE_OAUTH_TOKEN,CLAUDE_CONFIG_DIR) are set - Effective auth mode — what the next
vp run claudewill actually use
Exit codes: 0 healthy, 1 config dir missing, 2 OAuth token expired (useful in scripts).
Why this workaround exists¶
The root cause is in Claude Code itself, not in VibePod. The OAuth refreshToken is stored in .credentials.json but never used: the access token is loaded from disk, sent as-is until it 401s, and nothing is written back when a refresh would have succeeded. The bug affects native Linux, WSL, macOS, and every container-based deployment equally.
Community forensics (#33995 comment):
Set
expiresAtin~/.claude/.credentials.jsontoDate.now()to force expiry. Send a message — Claude processes it successfully, meaning the in-memory token refresh worked. Check~/.claude/.credentials.jsonafterward — file was never written. Conclusion:refreshOAuthTokensucceeds and returns new tokens, but the credential store'supdate()is never called (or silently fails) after a successful refresh. The new token lives only in memory. Next session launch reads the stale expired token from disk and requires re-login.
The community-validated workaround (#24317 comment) is exactly what VibePod implements:
I worked around this using
claude setup-tokenand then feeding it in as theCLAUDE_CODE_OAUTH_TOKENenvironment variable. It skips all the "OAuth tokens invalidating each other", but has the downside that it doesn't allow/usage.
claude setup-token itself is an officially supported Claude Code authentication path, documented for exactly this kind of non-interactive deployment. See Anthropic's authentication guide and the claude-code-action setup guide — the same mechanism used by Anthropic's own GitHub Action.
Upstream tracking issues — core bug (access-token not refreshed from disk):
| # | Status | Summary |
|---|---|---|
| #50743 | open · has repro · area:auth |
Newest and cleanest repro on headless Linux — refreshToken ignored |
| #42904 | closed as duplicate | Canonical "daily re-login required for subscription users" report |
| #40985 | open · stale |
"Auth tokens expire too frequently" — confirms ~8 h TTL |
| #33995 | closed not-planned | Best technical forensics (quoted above); proves write-back is the broken step |
| #21765 | closed not-planned | First clear statement: "Claude Code doesn't use refresh tokens to get new access tokens" |
| #12447 | open | OAuth expiry disrupts autonomous workflows; refresh token handling needed |
| #37402 | open | --print / automation mode also affected |
Multi-session race condition (why a shared .credentials.json across simultaneous sessions makes things worse):
| # | Status | Summary |
|---|---|---|
| #24317 | open · has repro · 18 comments |
Canonical thread; documents refresh-token rotation and single-use semantics |
| #48786 | closed as dup of #24317 | Independent reproduction |
| #27933 | closed | Early race-condition report |
| #45129 | closed as dup | Agent worktree subprocesses hit this constantly |
Container / headless specifically:
| # | Status | Summary |
|---|---|---|
| #22066 | closed as duplicate | OAuth authentication not persisting in Docker |
| #34917 | closed | OAuth "Redirect URI not supported" in headless/Docker |
| #34141 | closed | Claude Code ignores ANTHROPIC_API_KEY when OAuth redirect fails in devcontainers |
| #7100 | closed not-planned | Request for official headless-auth documentation |
| #22992 | open | Feature request: RFC 8628 device-code flow for headless |
Proxy / Cloudflare interaction (relevant if you run vibepod behind the built-in mitmproxy):
| # | Status | Summary |
|---|---|---|
| #47754 | open · area:auth · platform:linux |
Cloudflare WAF blocks OAuth token refresh from headless Linux servers |
| #33269 | open | Cloudflare challenge race during auth login / setup-token |
Anthropic's posture: most reports are auto-closed as duplicates by a bot; the core issues (#21765, #33995) were closed as "not planned." A changelog line for Claude Code v2.1.44 mentioned "Fixed auth refresh errors" but users report the same behaviour on every later version (v2.1.62, 2.1.74, 2.1.116 observed). No committed fix has landed as of this writing.
Gemini (Google)¶
OpenCode (OpenAI)¶
Devstral / Vibe (Mistral)¶
Note
Devstral runs under your host user (uid:gid) and requires the linux/amd64 platform. On Apple Silicon, Docker's Rosetta emulation is used automatically.
Auggie (Augment Code)¶
Copilot (GitHub)¶
Codex (OpenAI)¶
For browser OAuth login, run:
Codex binds its OAuth callback server to 127.0.0.1:1455 inside the container.
During codex login, VibePod publishes the expected host callback port and enables
a small container forwarder so the host browser can complete the redirect back to
Codex.
Device-code and API-key flows do not need the browser callback, so they skip the forwarder:
Pi (Earendil)¶
Pi runs the Pi coding agent from Earendil in the same isolated VibePod container workflow. Credentials and configuration are persisted under ~/.config/vibepod/agents/pi/.