Running Claude Code
This guide covers running Claude Code in a Moat container.
Prerequisites
- Moat installed
- Claude Code installed on your host machine with an active subscription (Claude Pro or Max), OR an Anthropic API key
Granting credentials
Moat uses two separate credential providers for Claude Code:
moat grant claude— OAuth tokens for Claude Pro/Max subscribersmoat grant anthropic— API keys from console.anthropic.com
Claude subscription (OAuth)
Run moat grant claude to authenticate with a Claude subscription. If the Claude CLI is installed, you see a menu:
$ moat grant claude
Choose authentication method:
1. Claude subscription (OAuth token)
Runs 'claude setup-token' to get a long-lived token.
2. Existing OAuth token
Paste a token from a previous 'claude setup-token' run.
3. Import existing Claude Code credentials
Import OAuth tokens from your local Claude Code installation.
Enter choice [1-3]:
Option 3 only appears if Claude Code credentials are found on your machine. Option 1 only appears if the Claude CLI is installed.
Option 1 runs claude setup-token, which may open a browser:
Running 'claude setup-token' to obtain authentication token...
This may open a browser for authentication.
Validating OAuth token...
OAuth token is valid.
Claude credential acquired via setup-token.
You can now run 'moat claude' to start Claude Code.
Option 2 prompts you to paste an OAuth token you already obtained.
Option 3 imports credentials from your local Claude Code installation:
Found Claude Code credentials.
Subscription: claude_pro
Expires: 2026-02-15T10:30:00Z
Validating OAuth token...
OAuth token is valid.
Claude Code credentials imported.
Note: Imported tokens do not auto-refresh. When the token expires, run a Claude Code session on your host machine to refresh it, then run moat grant claude again to import the new token.
Anthropic API key
Run moat grant anthropic to use an API key. This goes straight to the API key prompt:
$ moat grant anthropic
Enter your Anthropic API key.
You can find or create one at: https://console.anthropic.com/settings/keys
API Key: sk-ant-api...
Validating API key...
API key is valid.
You can also set ANTHROPIC_API_KEY in your environment before running the command.
How credentials are injected
The actual credential is never in the container environment. Moat’s proxy intercepts requests to Anthropic’s API and injects the real token at the network layer. See Credential management for details.
Generating moat.yaml
Use moat init to auto-generate a moat.yaml for your project:
moat init ./my-project
This scans the project, detects its dependencies and tools, and generates a configuration file using AI. Requires at least one credential granted (e.g., moat grant claude or moat grant anthropic).
Running Claude Code
Interactive mode
Start Claude Code in the current directory:
moat claude
Start in a specific project:
moat claude ./my-project
Claude Code launches in interactive mode with full access to the mounted workspace.
Non-interactive mode
Run with a prompt:
moat claude -p "explain this codebase"
moat claude -p "fix the failing tests"
moat claude -p "add input validation to the user registration form"
Claude Code executes the prompt and exits when complete.
Permission handling
By default, moat claude runs with --dangerously-skip-permissions enabled. This skips Claude Code’s per-tool confirmation prompts that normally ask before each file edit, command execution, or network request.
Security properties:
The container runs as a non-root user with filesystem access limited to the mounted workspace. Credentials are injected at the network layer and never appear in the container environment. See Security model for the full threat model.
Restoring manual approval:
If you prefer Claude Code’s default confirmation behavior, use the --noyolo flag:
moat claude --noyolo ./my-project
With --noyolo, Claude Code prompts for confirmation before each potentially destructive operation, just as it would when running directly on your host machine.
Resuming sessions
When Claude Code exits, Moat captures the session ID from the Claude projects directory on the host filesystem. You can resume a previous session by run name:
moat claude --resume my-feature
This looks up the stored Claude session UUID for the run named my-feature and passes it to Claude Code. You can also pass a raw Claude session UUID directly:
moat claude --resume ae150251-d90a-4f85-a9da-2281e8e0518d
To continue the most recent conversation without specifying a session:
moat claude --continue
Named runs
Give your run a name for reference:
moat claude --name feature-auth ./my-project
The name appears in moat list and makes it easier to manage multiple runs.
Non-interactive runs
Run Claude Code non-interactively with a prompt:
moat claude -p "fix the failing tests" ./my-project
Monitor progress:
$ moat list
NAME RUN ID STATE AGE
feature-auth run_a1b2c3d4e5f6 running 5m ago
$ moat logs -f run_a1b2c3d4e5f6
Adding GitHub access
Grant GitHub access so Claude Code can interact with repositories:
moat claude --grant github ./my-project
This injects GitHub credentials alongside Anthropic credentials. Claude Code can:
- Clone repositories
- Push commits
- Create pull requests
- Access private repositories
Configure in moat.yaml for repeated use:
name: my-claude-project
grants:
- anthropic
- github
Then:
moat claude ./my-project
Using an LLM proxy
Route Claude Code API traffic through a host-side proxy for caching, logging, or policy enforcement. Tools like Headroom sit between Claude Code and the Anthropic API.
Install and start Headroom:
pip install "headroom-ai[all]"
headroom proxy --port 8787
Configure claude.base_url in moat.yaml:
grants:
- claude
claude:
base_url: http://localhost:8787
Moat handles the details:
- Sets
ANTHROPIC_BASE_URLinside the container - Routes traffic through a relay on the Moat proxy (
localhostworks because the relay runs on the host) - Injects credentials for both
api.anthropic.comand the proxy host
Start the proxy on your host, then run as usual:
moat claude ./my-project
LLM response policy
Enforce policy on what Claude Code can do by evaluating tool_use blocks in Anthropic API responses. The proxy buffers each response, checks tool_use blocks against Keep rules, and denies responses that violate the policy before they reach the container.
claude:
llm-gateway:
policy: .keep/llm-rules.yaml
Inline rules work the same way as MCP policy:
claude:
llm-gateway:
policy:
deny: [Edit, Write, Bash]
mode: enforce
This blocks Claude Code from editing files, writing new files, or running shell commands. The agent receives a policy denial error and can adapt its approach.
The LLM gateway supports both JSON and SSE (streaming) responses and handles gzip-compressed bodies transparently. It is mutually exclusive with claude.base_url — both redirect LLM traffic, so only one can be active.
See the Keep documentation for the full rule format. See moat.yaml reference for configuration details.
Adding SSH access
For SSH-based git operations:
moat grant ssh --host github.com
moat claude --grant ssh:github.com ./my-project
Claude Code can use git@github.com:... URLs for cloning and pushing.
User settings
~/.moat/claude/settings.json is your personal Claude Code settings layer for moat containers. Any field you add to this file is forwarded into the container’s ~/.claude/settings.json as-is — you do not need to wait for moat to add explicit support for new Claude Code settings.
{
"enabledPlugins": {
"my-plugin@marketplace": true
},
"statusLine": {
"command": "node ~/.claude/moat/statusline.js"
}
}
In contrast, moat only reads plugin and marketplace fields from your host ~/.claude/settings.json. This prevents host-specific settings from leaking into containers.
Plugin management
Moat supports Claude Code plugins, with automatic discovery of plugins installed on your host machine.
Host plugin inheritance
Plugins you install via Claude Code on your host machine are automatically available in Moat containers:
# Install a plugin on your host
claude plugin marketplace add owner/repo
claude plugin enable plugin-name@repo
The next time you run moat claude, the plugin is available inside the container. Moat reads:
~/.claude/plugins/known_marketplaces.json— Marketplaces registered viaclaude plugin marketplace add~/.claude/settings.json— Plugin enable/disable settings
No additional configuration required. Moat detects plugin changes and rebuilds the image automatically on the next run.
Explicit plugin configuration
For reproducible builds or CI environments, configure plugins explicitly in moat.yaml:
claude:
plugins:
"plugin-name@marketplace": true
Settings in moat.yaml override host settings, giving you control over exactly which plugins are available.
Marketplaces
Configure additional plugin marketplaces:
claude:
marketplaces:
custom:
source: github
repo: owner/repo
Marketplace repos are cloned on the host before image build, using your local git credentials (gh auth, SSH keys, credential helpers).
This means private marketplace repos work without leaking credentials into the Docker image. Moat detects marketplace changes
and rebuilds the image automatically on the next run.
Language servers
Moat includes prepackaged language server configurations that give Claude Code access to code intelligence features like go-to-definition, find-references, and diagnostics. Language servers are installed as Claude Code plugins during image build.
Add language_servers to your moat.yaml:
agent: claude
language_servers:
- go
grants:
- anthropic
Moat installs the language server binary and its runtime dependencies during image build, then enables the corresponding Claude Code plugin. No additional setup is needed.
Available language servers
| Name | Language | Description | Dependencies installed |
|---|---|---|---|
go | Go | Code intelligence, refactoring, diagnostics | go, gopls |
typescript | TypeScript/JavaScript | Code intelligence, diagnostics | node, typescript, typescript-language-server |
python | Python | Code intelligence, type checking, diagnostics | python, pyright |
How it works
When you add a language server to language_servers:
- Moat adds required dependencies to the image build (e.g.,
goandgoplsfor the Go language server) - The corresponding Claude Code plugin is enabled and baked into the container image
- Claude Code discovers and manages the language server through its plugin system
Runtime dependencies are added automatically — listing them in dependencies: is not required. Moat detects language server changes and rebuilds the image automatically on the next run.
Multiple language servers
You can enable multiple language servers for polyglot projects:
language_servers:
- go
- typescript
- python
MCP servers
Moat supports both remote and local MCP servers with credential injection. See MCP servers for configuration and usage.
Workspace snapshots
Moat captures workspace snapshots for recovery and rollback. See Snapshots for configuration and usage.
Example: Code review workflow
-
Grant credentials:
moat grant anthropic moat grant github -
Create
moat.yaml:name: code-review grants: - anthropic - github snapshots: triggers: disable_pre_run: false -
Run Claude Code with a review prompt:
moat claude -p "Review the changes in the last 3 commits. Focus on security issues and suggest improvements." -
View what Claude Code did:
moat logs moat trace --network
Troubleshooting
”No Claude Code credentials found”
Claude Code is not installed or not logged in on your host machine. Either:
- Install Claude Code and log in, then run
moat grant anthropicagain - Use an API key:
export ANTHROPIC_API_KEY="sk-ant-..." && moat grant anthropic
”Credential expired”
OAuth credentials have an expiration time. Re-grant:
moat grant claude
Claude Code hangs on startup
Check that you’re not running in a directory without a moat.yaml that specifies a conflicting configuration. Try:
moat claude --name test ~/empty-dir
“Failed to install Anthropic marketplace”
Claude Code needs SSH access to GitHub to clone the official Anthropic plugin marketplace. Grant SSH access:
moat grant ssh --host github.com
Then add the grant to your moat.yaml:
grants:
- anthropic
- ssh:github.com
Or pass it on the command line:
moat claude --grant ssh:github.com ./my-project
Network errors
Verify the Anthropic credential is granted:
moat run --grant anthropic -- curl -s https://api.anthropic.com/v1/models
Related guides
- SSH access — Set up SSH for git operations
- Snapshots — Protect your workspace with snapshots
- MCP servers — Extend Claude Code with remote and local MCP servers
- Exposing ports — Access services running inside containers
- Security model — Container isolation and credential injection