Documentation
¶
Overview ¶
Package claude implements the Claude Code credential and agent provider.
The Claude provider acquires and manages Anthropic credentials for container runs. Credentials can be obtained from:
- OAuth tokens via `claude setup-token` (recommended for Pro/Max subscribers)
- Anthropic API keys from console.anthropic.com
- Importing existing Claude Code credentials from keychain or file
The provider configures the proxy to inject Bearer tokens (OAuth) or x-api-key headers (API keys) for api.anthropic.com. Containers receive placeholder tokens that pass local validation, while the real credentials are injected at the network layer by the proxy.
For OAuth tokens, the provider registers a response transformer to handle 403 errors on OAuth endpoints that require scopes not available in long-lived tokens. This allows Claude Code to degrade gracefully.
As an AgentProvider, this package also handles:
- Container preparation (staging directories, config files)
- Session management for Claude Code runs
- CLI commands (moat claude, moat claude sessions)
- Loading and merging Claude settings from multiple sources
- Generating Dockerfile snippets for plugin installation
Package claude handles Claude Code plugin and settings management.
Index ¶
- Constants
- Variables
- func ConfigureBaseURLProxy(p provider.ProxyConfigurer, cred *provider.Credential, baseURLHost string)
- func CreateOAuthEndpointTransformer() func(req, resp interface{}) (interface{}, bool)
- func LoadKnownMarketplaces(path string) (map[string]MarketplaceEntry, error)
- func ReadHostConfig(path string) (map[string]any, error)
- func WorkspaceToClaudeDir(absPath string) string
- func WriteClaudeConfig(stagingDir string, mcpServers map[string]MCPServerForContainer, ...) error
- func WriteCredentialsFile(cred *provider.Credential, stagingDir string) error
- type AnthropicProvider
- func (p *AnthropicProvider) Cleanup(cleanupPath string)
- func (p *AnthropicProvider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)
- func (p *AnthropicProvider) ContainerEnv(cred *provider.Credential) []string
- func (p *AnthropicProvider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)
- func (p *AnthropicProvider) Grant(ctx context.Context) (*provider.Credential, error)
- func (p *AnthropicProvider) ImpliedDependencies() []string
- func (p *AnthropicProvider) Name() string
- type KnownMarketplace
- type KnownMarketplaceSource
- type KnownMarketplacesFile
- type MCPServerForContainer
- type MarketplaceConfig
- type MarketplaceEntry
- type MarketplaceSource
- type OAuthProvider
- func (p *OAuthProvider) Cleanup(cleanupPath string)
- func (p *OAuthProvider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)
- func (p *OAuthProvider) ContainerEnv(cred *provider.Credential) []string
- func (p *OAuthProvider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)
- func (p *OAuthProvider) Grant(ctx context.Context) (*provider.Credential, error)
- func (p *OAuthProvider) ImpliedDependencies() []string
- func (p *OAuthProvider) Name() string
- func (p *OAuthProvider) OnRunStopped(ctx provider.RunStoppedContext) map[string]string
- func (p *OAuthProvider) PrepareContainer(ctx context.Context, opts provider.PrepareOpts) (*provider.ContainerConfig, error)
- func (p *OAuthProvider) RegisterCLI(root *cobra.Command)
- type PluginSnippetResult
- type SettingSource
- type Settings
Constants ¶
const ClaudeInitMountPath = "/moat/claude-init"
ClaudeInitMountPath is the path where the Claude staging directory is mounted. The moat-init script reads from this path and copies files to ~/.claude.
const ClaudeMarketplacesPath = ClaudePluginsPath + "/marketplaces"
ClaudeMarketplacesPath is the path where marketplaces are mounted in the container.
const ClaudePluginsPath = "/home/moatuser/.claude/plugins"
ClaudePluginsPath is the base path for Claude plugins in the container. This matches Claude Code's expected location at ~/.claude/plugins. We use the absolute path for moatuser since that's our standard container user.
const ProxyInjectedPlaceholder = "moat-proxy-injected"
ProxyInjectedPlaceholder is a placeholder value for credentials that will be injected by the Moat proxy at runtime.
Variables ¶
var HostConfigAllowlist = []string{
"oauthAccount",
"userID",
"anonymousId",
"installMethod",
"lastOnboardingVersion",
"lastReleaseNotesSeen",
"numStartups",
"sonnet45MigrationComplete",
"opus45MigrationComplete",
"opusProMigrationComplete",
"thinkingMigrationComplete",
"clientDataCache",
"cachedGrowthBookFeatures",
"firstStartTime",
}
HostConfigAllowlist lists fields from the host's ~/.claude.json that are safe and useful to copy into containers. These avoid startup API calls and ensure consistent behavior.
Fields are categorized by purpose:
- OAuth authentication: oauthAccount, userID, anonymousId (required for x-organization-uuid header)
- Installation tracking: installMethod, lastOnboardingVersion, numStartups (affects auth behavior)
- Feature flags: migration flags, clientDataCache (contains system_prompt_variant)
- Performance: cachedGrowthBookFeatures (optional, reduces startup API calls)
var OAuthEndpointWorkarounds = []string{
"/api/oauth/profile",
"/api/oauth/usage",
}
OAuthEndpointWorkarounds defines OAuth API endpoints that require response transformation to work around scope limitations in long-lived tokens.
Background ¶
Long-lived tokens created via `claude setup-token` do not include the `user:profile` scope, causing 403 "permission_error" responses on these endpoints. However, these endpoints are non-critical for core Claude Code functionality - they provide usage statistics and profile information for the UI status line and user display.
Why Transform Instead of Fail ¶
Rather than forcing users to re-authenticate with different scopes (which `claude setup-token` doesn't support anyway) or causing hard crashes in Claude Code's status line, we intercept 403 permission errors on these specific endpoints and return empty success responses. This allows Claude Code to degrade gracefully: no usage stats displayed, but no crashes either.
Security Consideration ¶
This transformation only applies to: 1. These explicitly listed endpoints 2. 403 status codes (all 403s on these endpoints, not just permission errors) 3. Requests using OAuth tokens (not API keys)
Other errors (401, 500, etc.) pass through unchanged to preserve observability.
Functions ¶
func ConfigureBaseURLProxy ¶ added in v0.3.0
func ConfigureBaseURLProxy(p provider.ProxyConfigurer, cred *provider.Credential, baseURLHost string)
ConfigureBaseURLProxy registers credential injection for a custom base URL host, mirroring the standard api.anthropic.com injection. This is called by the run manager when claude.base_url is configured, so that a host-side LLM proxy receives requests with credentials already injected.
The function checks cred.Provider to determine the correct header format: - "claude" (OAuth): Bearer Authorization header + beta flag + response transformer - "anthropic" (API key): x-api-key header
func CreateOAuthEndpointTransformer ¶
func CreateOAuthEndpointTransformer() func(req, resp interface{}) (interface{}, bool)
CreateOAuthEndpointTransformer creates a response transformer that handles 403 errors on OAuth endpoints by returning empty success responses.
The transformer: 1. Only acts on 403 status codes 2. Checks if the request path matches one of OAuthEndpointWorkarounds 3. Returns an empty but valid JSON response for that endpoint 4. Adds X-Moat-Transformed header for observability
We don't check the response body because: - These are explicitly listed OAuth endpoints (not wildcards) - Any 403 on these endpoints is almost certainly a scope issue - Body checking requires handling gzip/compression which adds complexity - Transforming a non-scope 403 is harmless (returns empty data, no crash)
Original 403 responses are still logged for debugging, but the client receives a success response to prevent crashes.
func LoadKnownMarketplaces ¶
func LoadKnownMarketplaces(path string) (map[string]MarketplaceEntry, error)
LoadKnownMarketplaces loads Claude's known_marketplaces.json file. This file contains marketplace URLs that Claude Code has registered via `claude plugin marketplace add`. Returns nil, nil if the file doesn't exist.
URL normalization: - "github" sources are normalized to git URLs (https://github.com/owner/repo.git) - We assume repos don't contain trailing slashes or .git suffixes (Claude CLI standard) - Git URLs are used as-is without normalization
Entries are skipped (with debug logging) if they have: - Empty repo/URL fields - Invalid characters in repo format (shell injection protection)
func ReadHostConfig ¶
ReadHostConfig reads the host's ~/.claude.json and returns allowlisted fields. Returns nil, nil if the file doesn't exist (same pattern as LoadSettings).
func WorkspaceToClaudeDir ¶ added in v0.3.0
WorkspaceToClaudeDir converts an absolute workspace path to Claude's project directory format. Example: /home/alice/projects/myapp -> -home-alice-projects-myapp
func WriteClaudeConfig ¶
func WriteClaudeConfig(stagingDir string, mcpServers map[string]MCPServerForContainer, hostConfig map[string]any) error
WriteClaudeConfig writes a minimal ~/.claude.json to the staging directory. This skips the onboarding flow, sets dark theme, and optionally configures MCP servers. mcpServers is a map of server names to their configurations. hostConfig contains allowlisted fields from the host's ~/.claude.json to merge in.
func WriteCredentialsFile ¶
func WriteCredentialsFile(cred *provider.Credential, stagingDir string) error
WriteCredentialsFile writes a placeholder credentials file to the staging directory. This should only be called for OAuth tokens - API keys don't need credential files.
SECURITY: The real OAuth token is NEVER written to the container filesystem. Authentication is handled by the TLS-intercepting proxy at the network layer.
Types ¶
type AnthropicProvider ¶ added in v0.3.0
type AnthropicProvider struct{}
AnthropicProvider implements provider.CredentialProvider for Anthropic API keys.
API keys work with any tool or agent — they are not restricted to Claude Code. This provider uses the x-api-key header with no OAuth workarounds.
func (*AnthropicProvider) Cleanup ¶ added in v0.3.0
func (p *AnthropicProvider) Cleanup(cleanupPath string)
Cleanup cleans up Anthropic resources.
func (*AnthropicProvider) ConfigureProxy ¶ added in v0.3.0
func (p *AnthropicProvider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)
ConfigureProxy sets up proxy headers for API keys on the Anthropic API.
func (*AnthropicProvider) ContainerEnv ¶ added in v0.3.0
func (p *AnthropicProvider) ContainerEnv(cred *provider.Credential) []string
ContainerEnv returns environment variables for API key injection.
func (*AnthropicProvider) ContainerMounts ¶ added in v0.3.0
func (p *AnthropicProvider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)
ContainerMounts returns mounts needed for the Anthropic provider (none).
func (*AnthropicProvider) Grant ¶ added in v0.3.0
func (p *AnthropicProvider) Grant(ctx context.Context) (*provider.Credential, error)
Grant acquires an Anthropic API key interactively.
func (*AnthropicProvider) ImpliedDependencies ¶ added in v0.3.0
func (p *AnthropicProvider) ImpliedDependencies() []string
ImpliedDependencies returns dependencies implied by the Anthropic provider.
func (*AnthropicProvider) Name ¶ added in v0.3.0
func (p *AnthropicProvider) Name() string
Name returns the provider identifier.
type KnownMarketplace ¶
type KnownMarketplace struct {
Source KnownMarketplaceSource `json:"source"`
InstallLocation string `json:"installLocation"`
LastUpdated string `json:"lastUpdated"`
}
KnownMarketplace represents a single marketplace entry in known_marketplaces.json.
type KnownMarketplaceSource ¶
type KnownMarketplaceSource struct {
Source string `json:"source"` // "github" or "git"
Repo string `json:"repo,omitempty"`
URL string `json:"url,omitempty"`
}
KnownMarketplaceSource is the source info in known_marketplaces.json.
type KnownMarketplacesFile ¶
type KnownMarketplacesFile struct {
Marketplaces map[string]KnownMarketplace
}
KnownMarketplacesFile documents the ~/.claude/plugins/known_marketplaces.json format.
This file is created and maintained by Claude Code when users run `claude plugin marketplace add <repo>`. It stores the repository URLs for installed marketplaces, allowing moat to know where to fetch plugins from.
Note: This is an internal Claude Code file format that may change between versions. We parse only the fields we need (source info) and ignore others.
This type is defined for documentation; LoadKnownMarketplaces() parses the JSON directly into a map[string]KnownMarketplace.
type MCPServerForContainer ¶
type MCPServerForContainer struct {
Type string `json:"type"`
URL string `json:"url,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
}
MCPServerForContainer represents an MCP server in Claude's .claude.json format. Supports both HTTP relay servers (type: "http") and local process servers (type: "stdio").
type MarketplaceConfig ¶
type MarketplaceConfig struct {
Name string // Marketplace name (e.g., "claude-plugins-official")
Source string // "github" or "git"
Repo string // Repository path (e.g., "anthropics/claude-plugins-official")
}
MarketplaceConfig represents a Claude Code plugin marketplace for image building.
type MarketplaceEntry ¶
type MarketplaceEntry struct {
Source MarketplaceSource `json:"source"`
}
MarketplaceEntry represents a marketplace in Claude's settings format.
type MarketplaceSource ¶
type MarketplaceSource struct {
// Source is the type: "git", "github", or "directory"
Source string `json:"source"`
// URL is the git URL (for source: git or github)
URL string `json:"url,omitempty"`
// Repo is the GitHub owner/repo shorthand (for source: github)
// Claude Code's native settings.json uses this format.
Repo string `json:"repo,omitempty"`
// Path is the local directory path (for source: directory)
Path string `json:"path,omitempty"`
}
MarketplaceSource defines the source location for a marketplace.
type OAuthProvider ¶ added in v0.3.0
type OAuthProvider struct{}
OAuthProvider implements provider.CredentialProvider and provider.AgentProvider for Claude Code OAuth tokens (from Claude Pro/Max subscriptions).
OAuth tokens are restricted by Anthropic's ToS to Claude Code and Claude.ai only. This provider uses Bearer auth with the required beta flag and response transformer.
func (*OAuthProvider) Cleanup ¶ added in v0.3.0
func (p *OAuthProvider) Cleanup(cleanupPath string)
Cleanup cleans up Claude resources.
func (*OAuthProvider) ConfigureProxy ¶ added in v0.3.0
func (p *OAuthProvider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)
ConfigureProxy sets up proxy headers for OAuth tokens on the Anthropic API.
func (*OAuthProvider) ContainerEnv ¶ added in v0.3.0
func (p *OAuthProvider) ContainerEnv(cred *provider.Credential) []string
ContainerEnv returns environment variables for OAuth token injection.
func (*OAuthProvider) ContainerMounts ¶ added in v0.3.0
func (p *OAuthProvider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)
ContainerMounts returns mounts needed for Claude Code. This method returns empty because Claude Code setup uses the staging directory approach instead of direct mounts.
func (*OAuthProvider) Grant ¶ added in v0.3.0
func (p *OAuthProvider) Grant(ctx context.Context) (*provider.Credential, error)
Grant acquires a Claude Code OAuth token interactively. Offers OAuth-specific options: setup-token, paste existing token, or import from local Claude Code installation.
func (*OAuthProvider) ImpliedDependencies ¶ added in v0.3.0
func (p *OAuthProvider) ImpliedDependencies() []string
ImpliedDependencies returns dependencies implied by the Claude OAuth provider.
func (*OAuthProvider) Name ¶ added in v0.3.0
func (p *OAuthProvider) Name() string
Name returns the provider identifier.
func (*OAuthProvider) OnRunStopped ¶ added in v0.3.0
func (p *OAuthProvider) OnRunStopped(ctx provider.RunStoppedContext) map[string]string
OnRunStopped extracts the Claude session ID from the projects directory after the container exits. It implements provider.RunStoppedHook.
func (*OAuthProvider) PrepareContainer ¶ added in v0.3.0
func (p *OAuthProvider) PrepareContainer(ctx context.Context, opts provider.PrepareOpts) (*provider.ContainerConfig, error)
PrepareContainer sets up staging directories and config files for Claude Code. It creates the necessary files that will be copied into the container at startup.
If opts.HostConfig is nil, this method reads the host's ~/.claude.json automatically.
This method works with both OAuth tokens and API keys. The credential type determines which environment variable placeholder is set.
func (*OAuthProvider) RegisterCLI ¶ added in v0.3.0
func (p *OAuthProvider) RegisterCLI(root *cobra.Command)
RegisterCLI adds provider-specific commands to the root command. This adds the `moat claude` command group with subcommands.
type PluginSnippetResult ¶
type PluginSnippetResult struct {
// DockerfileSnippet is the Dockerfile text to append (COPY + RUN).
DockerfileSnippet string
// ScriptName is the context file name (empty if no plugins).
ScriptName string
// ScriptContent is the shell script content (nil if no plugins).
ScriptContent []byte
}
PluginSnippetResult holds the Dockerfile snippet and optional script context file.
func GenerateDockerfileSnippet ¶
func GenerateDockerfileSnippet(marketplaces []MarketplaceConfig, plugins []string, containerUser string) PluginSnippetResult
GenerateDockerfileSnippet generates Dockerfile commands for Claude Code plugin installation. Returns an empty result if no marketplaces or plugins are configured.
Plugin install commands are written to a separate shell script (returned as a context file) rather than inline Dockerfile RUN steps. This keeps the Dockerfile under the Apple containers builder's ~16KB gRPC transport limit which causes "Transport became inactive" errors for larger Dockerfiles.
The containerUser parameter specifies the user to install plugins as. This is used in Dockerfile USER and WORKDIR commands. Callers must ensure this is a safe, validated value (e.g., hardcoded "moatuser") since it's inserted directly into the Dockerfile. The function does not validate this parameter to allow flexibility in user naming.
type SettingSource ¶
type SettingSource string
SettingSource identifies where a setting came from.
const ( SourceClaudeUser SettingSource = "~/.claude/settings.json" SourceMoatUser SettingSource = "~/.moat/claude/settings.json" SourceProject SettingSource = ".claude/settings.json" SourceMoatYAML SettingSource = "moat.yaml" SourceUnknown SettingSource = "unknown" )
type Settings ¶
type Settings struct {
// EnabledPlugins maps "plugin-name@marketplace" to enabled/disabled state
EnabledPlugins map[string]bool `json:"enabledPlugins,omitempty"`
// ExtraKnownMarketplaces defines additional plugin marketplaces
ExtraKnownMarketplaces map[string]MarketplaceEntry `json:"extraKnownMarketplaces,omitempty"`
// PluginSources tracks where each plugin setting came from (not serialized)
PluginSources map[string]SettingSource `json:"-"`
// MarketplaceSources tracks where each marketplace setting came from (not serialized)
MarketplaceSources map[string]SettingSource `json:"-"`
}
Settings represents Claude's native settings.json format. This is the format used by Claude Code in .claude/settings.json files.
func ConfigToSettings ¶
ConfigToSettings converts moat.yaml claude config to Settings format.
func LoadAllSettings ¶
LoadAllSettings loads and merges settings from all sources. Merge precedence (lowest to highest): 1. ~/.claude/plugins/known_marketplaces.json (Claude's registered marketplaces) 2. ~/.claude/settings.json (Claude's native user settings) 3. ~/.moat/claude/settings.json (user defaults for moat runs) 4. <workspace>/.claude/settings.json (project defaults) 5. moat.yaml claude.* fields (run overrides)
func LoadSettings ¶
LoadSettings loads a single Claude settings.json file. Returns nil, nil if the file doesn't exist.
func MergeSettings ¶
func MergeSettings(base, override *Settings, overrideSource SettingSource) *Settings
MergeSettings merges two settings objects with override taking precedence. This implements the merge rules: - enabledPlugins: Union all sources; later overrides earlier for same plugin - extraKnownMarketplaces: Union all sources; later overrides earlier for same name The overrideSource is used to track where override settings came from.
func (*Settings) GetMarketplaceNames ¶
GetMarketplaceNames returns the names of all marketplaces referenced in settings. This includes marketplaces from extraKnownMarketplaces and those inferred from plugin names.
func (*Settings) HasPluginsOrMarketplaces ¶
HasPluginsOrMarketplaces returns true if the settings contain any plugins or marketplaces.