ClaudeWrapper.Settings (ClaudeWrapper v0.8.0)

Copy Markdown View Source

Read-side access to Claude Code's on-disk settings files.

Claude Code reads up to four JSON files in increasing order of precedence (later layers override earlier ones):

  1. ~/.claude/settings.json -- user defaults
  2. ~/.claude/settings.local.json -- user-private overrides
  3. <project>/.claude/settings.json -- project-shared
  4. <project>/.claude/settings.local.json -- project-private

This module reads each layer as an opaque decoded JSON value and returns them side by side, without merging. Claude Code's merge semantics are non-trivial and not fully documented, so reproducing them here would risk diverging from the binary. Callers who want an "effective" view can merge with full knowledge of which layer produced which value -- the per-layer split makes that attribution possible.

Secrets

The env block of a settings file often contains secrets (ANTHROPIC_API_KEY, tokens). Use redact_env_values/1 before forwarding a layer to a less-trusted consumer.

Example

{:ok, settings} = ClaudeWrapper.Settings.load(project_root: "/path/to/repo")

case ClaudeWrapper.Settings.get(settings, :user) do
  nil -> IO.puts("no user settings")
  user -> IO.inspect(Map.keys(user))
end

Summary

Types

One of the four settings layers, low-to-high precedence.

Absolute paths the loader checked, whether or not the files exist.

t()

Functions

The filename component (after .claude/) for a layer.

Return the loaded value for one layer/0, or nil if absent.

All four layers, low-to-high precedence.

Load all four settings layers.

Replace every value under the top-level env object with "<redacted>", keeping the keys visible. A no-op on values that are not maps or that have no map-valued env field.

Types

layer()

@type layer() :: :user | :user_local | :project | :project_local

One of the four settings layers, low-to-high precedence.

paths()

@type paths() :: %{
  user: String.t(),
  user_local: String.t(),
  project: String.t() | nil,
  project_local: String.t() | nil
}

Absolute paths the loader checked, whether or not the files exist.

t()

@type t() :: %ClaudeWrapper.Settings{
  paths: paths(),
  project: map() | nil,
  project_local: map() | nil,
  user: map() | nil,
  user_local: map() | nil
}

Functions

filename(layer)

@spec filename(layer()) :: String.t()

The filename component (after .claude/) for a layer.

get(s, atom)

@spec get(t(), layer()) :: map() | nil

Return the loaded value for one layer/0, or nil if absent.

layers()

@spec layers() :: [layer(), ...]

All four layers, low-to-high precedence.

load(opts \\ [])

@spec load(keyword()) :: {:ok, t()} | {:error, ClaudeWrapper.Error.t()}

Load all four settings layers.

Options:

  • :user_root -- the .claude directory to read user layers from (default: ~/.claude)
  • :project_root -- the project directory whose .claude/ holds the project layers (default: none, so the project layers stay nil)

Missing files become nil; a malformed JSON file returns {:error, %ClaudeWrapper.Error{kind: :invalid_settings_json}} (with :reason %{path:, error:}). Returns {:error, %ClaudeWrapper.Error{kind: :no_home}} when :user_root is omitted and the home directory cannot be determined.

redact_env_values(map)

@spec redact_env_values(map()) :: map()

Replace every value under the top-level env object with "<redacted>", keeping the keys visible. A no-op on values that are not maps or that have no map-valued env field.