Skip to main content
Guides

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 subscribers
  • moat 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_URL inside the container
  • Routes traffic through a relay on the Moat proxy (localhost works because the relay runs on the host)
  • Injects credentials for both api.anthropic.com and 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 via claude 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

NameLanguageDescriptionDependencies installed
goGoCode intelligence, refactoring, diagnosticsgo, gopls
typescriptTypeScript/JavaScriptCode intelligence, diagnosticsnode, typescript, typescript-language-server
pythonPythonCode intelligence, type checking, diagnosticspython, pyright

How it works

When you add a language server to language_servers:

  1. Moat adds required dependencies to the image build (e.g., go and gopls for the Go language server)
  2. The corresponding Claude Code plugin is enabled and baked into the container image
  3. 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

  1. Grant credentials:

    moat grant anthropic
    moat grant github
  2. Create moat.yaml:

    name: code-review
    
    grants:
      - anthropic
      - github
    
    snapshots:
      triggers:
        disable_pre_run: false
  3. 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."
  4. 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:

  1. Install Claude Code and log in, then run moat grant anthropic again
  2. 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
  • 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