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 CreateOAuthEndpointTransformer() func(req, resp interface{}) (interface{}, bool)
- func LoadKnownMarketplaces(path string) (map[string]MarketplaceEntry, error)
- func ReadHostConfig(path string) (map[string]any, error)
- func WriteClaudeConfig(stagingDir string, mcpServers map[string]MCPServerForContainer, ...) error
- func WriteCredentialsFile(cred *provider.Credential, stagingDir string) error
- type KnownMarketplace
- type KnownMarketplaceSource
- type KnownMarketplacesFile
- type MCPServerForContainer
- type MarketplaceConfig
- type MarketplaceEntry
- type MarketplaceSource
- type PluginSnippetResult
- type Provider
- func (p *Provider) Cleanup(cleanupPath string)
- func (p *Provider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)
- func (p *Provider) ContainerEnv(cred *provider.Credential) []string
- func (p *Provider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)
- func (p *Provider) Grant(ctx context.Context) (*provider.Credential, error)
- func (p *Provider) ImpliedDependencies() []string
- func (p *Provider) Name() string
- func (p *Provider) PrepareContainer(ctx context.Context, opts provider.PrepareOpts) (*provider.ContainerConfig, error)
- func (p *Provider) RegisterCLI(root *cobra.Command)
- 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 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 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 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"`
Headers map[string]string `json:"headers,omitempty"`
}
MCPServerForContainer represents an MCP server in Claude's .claude.json format.
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"`
// Path is the local directory path (for source: directory)
Path string `json:"path,omitempty"`
}
MarketplaceSource defines the source location for a marketplace.
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 Provider ¶
type Provider struct{}
Provider implements provider.CredentialProvider and provider.AgentProvider for Claude Code / Anthropic credentials.
func (*Provider) ConfigureProxy ¶
func (p *Provider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)
ConfigureProxy sets up proxy headers for Anthropic API.
func (*Provider) ContainerEnv ¶
func (p *Provider) ContainerEnv(cred *provider.Credential) []string
ContainerEnv returns environment variables for Anthropic.
func (*Provider) ContainerMounts ¶
func (p *Provider) 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. The staging directory is populated by PrepareContainer and copied to the container at startup by moat-init.
func (*Provider) ImpliedDependencies ¶
ImpliedDependencies returns dependencies implied by the Claude provider. Claude Code requires node.js runtime.
func (*Provider) PrepareContainer ¶
func (p *Provider) 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.
func (*Provider) RegisterCLI ¶
RegisterCLI adds provider-specific commands to the root command. This adds the `moat claude` command group with subcommands.
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" SourceAgentYAML SettingSource = "agent.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 agent.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. agent.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.