Skip to content

Settings cascade ​

If you already have a Claude Code-style settings.json, dsh-cc reads it. Settings come from five levels, merged from low to high, with a Claude Code-compatible permissions schema, camelCase key aliasing, and a guarded env section. This page covers where the files live, how they merge, and the two rules most likely to surprise you (env gating and key aliasing).

The five levels ​

The settings cascade plugin (@dsh-cc/settings-cascade) merges five file sources from low to high — user < project < local < flag < policy — over a plugin-default base:

#LevelPathNotes
1User$DSH_HOME/settings.json (default ~/.dsh/settings.json)The only writable layer; update()/persist() writes land here.
2Project<cwd>/.claude/settings.jsonStays at the launch directory.
3Local<git-main-checkout-root-or-toplevel>/.claude/settings.local.jsonThe gitignored per-user file; hoisted git-style (see below).
4Flagthe --settings file, plus inline --settings content merged over itSession-only.
5Policyfirst non-empty of remote settings > system managed file > user-writable managed fileHighest priority.

Higher wins. Sources deep-merge recursively: a higher level fills keys a lower one omitted and replaces values it does carry. Plugin registration base and schema defaults resolve below every file level, so a missing document still resolves exactly as a leaf provider would.

Git-style hoisting for settings.local.json

When the launch directory is inside a git repo, the local file is read from the git main checkout root (worktree) or toplevel (launched in a subdirectory), matching Claude Code. Paths inside the file still resolve against the launch directory. Hoisting falls back to the launch directory when the repo root is $HOME, on Windows, or when ownership of the repo root/.git/.claude cannot be confirmed (fail-closed).

Merge rules worth knowing ​

  • Permission arrays union, deny wins. allow, deny, and ask arrays concatenate and deduplicate across levels; the unioned deny set is removed from allow — a higher-level deny always beats a lower-level allow.
  • Other arrays are replaced. For example additionalDirectories: the higher level overrides wholesale.
  • Misconfiguration fails loud. A present-but-unparsable settings document (bad JSON, non-object root) fails plugin load. An absent file contributes nothing and is not an error.
  • Writes are surgical deltas onto the user file. Only keys the caller actually changed are written to ~/.dsh/settings.json; inherited values from higher levels are not copied in. Concurrent writes from multiple dsh processes can silently lose updates (single-process profiles are unaffected), and unsetting a key inherited from a lower level does not persist across restart.

enabledPlugins and plugin state ​

The enabledPlugins key (exact name@marketplace ids) gates which installed Claude Code-format plugins mount at startup. For plugin discovery the cascade gains a dsh layer: claude-user → dsh-user → project → local. All /plugin mutations write to the dsh home only ($DSH_HOME / ~/.dsh, including user-scope enabledPlugins / extraKnownMarketplaces entries in ~/.dsh/settings.json); the Claude home stays read-visible and is never written. Details: Plugins.

Hot reload ​

A file watcher (chokidar) watches every concrete settings file that contributes to the merge. When one changes on disk, the cascade reloads and re-merges without a restart. A malformed or unreadable source during reload never takes a live session down: the last good document stays published, the failure is logged as a warning, and the watcher keeps watching. Reloads and persists are serialized on a single operation chain, so a persist racing an external edit retries on fresh bytes instead of clobbering them.

The env section: two-phase application ​

A top-level env section in any settings file is split out of the merged document and exposed as string-coerced key/value pairs. It is applied in two phases:

  1. applyEnv() assigns ordinary variables — applied normally.
  2. applyTrustedEnv() additionally assigns environment-altering variables and runs only after you grant trust.

The second phase is gated by DANGEROUS_ENV_VARS, a static allowlist of variables that alter process behavior or library loading. It includes (verbatim):

LD_PRELOAD
DYLD_INSERT_LIBRARIES
PATH

(also LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, PYTHONPATH, NODE_OPTIONS, NODE_PATH, RUBYLIB, PERL5LIB). The list is fixed; deployment-specific variables need an explicit extension point before first use.

Key aliasing: statusLine → statusline ​

To let a settings.json shared verbatim with a real Claude Code checkout work, recognized camelCase top-level keys are copied onto dsh-native kebab-case namespaces. The whitelist map is the contract — no fuzzy matching, unknown camelCase keys are never aliased:

json
{
  "statusLine": {
    "type": "command",
    "command": "~/.dsh/statusline.sh",
    "padding": 0,
    "refreshInterval": 10
  }
}

Rules, exactly:

  • The alias is injected only when the camelCase key's value is a plain object and the kebab key is absent from the merged document.
  • A dsh-native key already present wins untouched (so statusline beats statusLine).
  • Non-object values (e.g. a string statusLine) are ignored.

Currently there is exactly one entry in the whitelist: statusLine → statusline. On the tui profile this key replaces the built-in bottom status line with your own shell command; it can live in ~/.dsh/settings.json or a project .claude/settings.json.

Plugin enablement: enabledPlugins ​

Plugin discovery reads the enabledPlugins map with a dual-home layering: claude-user ($CLAUDE_CONFIG_DIR / ~/.claude) → dsh-user ($DSH_HOME / ~/.dsh/settings.json) → project → local, later files overriding per key. /plugin mutations write only under the dsh home — user-scope enabledPlugins and extraKnownMarketplaces entries land in ~/.dsh/settings.json, and the Claude home stays read-visible, never written. The full dual-home rules are in Plugins.

Settings migrations: removed in v0.8.3 ​

The mechanism-only @dsh-cc/settings-migrations package was removed in dsh-cc v0.8.3. Its registry had been empty since it shipped — no concrete migrations ever existed for cc or dsh. The capability will return when the first real settings-shape change ships.

Profile-level tweaks ​

Beyond settings.json, your profile remains ordinary dsh composition. Local tweaks can be placed in:

text
~/.dsh/profiles/tui/cordis.patch.yml

They are applied after the installed bundles. The microcompaction pass (@dsh-cc/compaction-micro: retainResults 10, auto false, placeholderChars 256, failureCap 3) is tuned through this profile cordis config like the other preset rows above — it has no settings namespace.

Preset feature namespaces ​

Several CC preset features are tuned through their own settings namespaces in ~/.dsh/settings.json:

NamespaceKeys (defaults)What it tunesMore
cc-onboardingsuppressed (unset/false)Set true to permanently skip the first-run onboarding flow; re-arm with /onboard.Quick start
cc-learnenabled (true), days (14), min-occurrences (2)The /learn command's recency window and write gating; enabled also gates the manage_skill tool.Memory
cc-skill-usageenabled (true), rollup-stale-hours (24), never-loaded-days (30)Skill-usage telemetry: counts committed skill loads into per-workspace sidecar ledgers and derives a utility report whose demote-candidate list is report-only. Main-realm only; user layer only.Skills
cc-handoffenabled (true), threshold-chars (8192, advisory)Subagent handoff store availability and its advisory size threshold.Subagents
cc-context-compressionenabled (false), mode (dry-run), min-bytes (8192), min-savings-ratio (0.4), protected-tools, reducer-enabled (false), reducer-max-input-tokens (30000), reducer-min-savings-ratio (0.5), reducer-max-tokens (1024), reducer-timeout-ms (10000), reducer-alias (haiku), reducer-commands, defer-requests (0), defer-margin (1.5), defer-max-age-ms (1800000), defer-urgency-tokens (unset)Opt-in reversible compression of large tool results. The reducer-* keys tune an evidence-preserving pass that sends oversized results to a cheap-lane side query; all ship dark (reducer-enabled false by default). The defer-* keys defer externalization, so an eligible result enters the session unmodified and swaps to the standard stub after that many counted main-loop sends (0 disables deferral).Interactive basics
model-aliasesalias entries plus warnOnInherit (true)The settings overlay backing alias resolution; warnOnInherit: false suppresses the cheap-lane inherit warning when an alias falls back to the parent route.Model routing
moaenabled (false), acceptance.enabled (false), acceptance.shadow (false), acceptance.tau (0.7), max-escalations (1), judge-route (gauge), classify-budget-tokens (4000), call-budget-ms (8000)Tiered cascade routing for the main conversation: a System One classify call picks the cheapest sufficient lane (sketch/draft/blueprint/masterplan), and an optional acceptance judge escalates a rejected answer one tier up. Ships dark; subagents are never overlaid.Model routing
worktreebaseRef (fresh), cleanupPeriodDays (30)Worktree base selection (fresh uses a cached origin/HEAD with a stale refresh) and the boot-time sweep of stale worktree-* worktrees.Worktrees
actor-contractmodels (['glm-*'])Which models receive <!-- actor-contract --> persona blocks in agent definitions; an empty list disables the contract.Subagents
cc-tool-use-summaryenabled (true), topLevelOnly (true), minResultBytes (4096), retentionDays (7), and moreCheap-lane digests of large tool results, consumed at compaction; topLevelOnly skips subagent sessions and retentionDays: 0 keeps the ledger in memory only.Interactive basics
cc-prompt-suggestenabled (false), alias (haiku), timeoutMs (4000), maxTokens (128)Opt-in next-prompt suggestion at turn-stop. Read through the full settings cascade (not restricted to the user layer, unlike the two plugins below).Interactive basics
cc-post-edit-verifyenabled (false), rules ([]), debounce-ms (5000)Runs a user-declared verification command after accepted edit/write results and appends the outcome to the same tool result. User layer only (~/.dsh/settings.json); project-scope rules are never read.Interactive basics
cc-edit-recovery-hintenabled (true)Appends static recovery advice when edit fails with a not-found error on a multi-line old_string. Default ON since 2026-10-16 (was ship-dark). User layer only; project scope is never read.Interactive basics
cc-compaction-cost-gateenabled (false), mode (dry-run), margin (1.0), cooldown-ms (600000)Ships dark; evaluates whether projected compaction savings justify the rewrite cost before calling /compact-style compaction, with a cooldown after each real pass.Interactive basics
cc-completion-gateenabled (false), nudges-per-session (1)Receipt ledger plus one nudge per session when a turn-stop claims done-ness ("tests pass", "committed", …) with no matching executed evidence. User layer only.Interactive basics
retry-attendantenabled (true), guidance (true), escalate (true), expire-minutes (10)Verify-before-retry attendant for mutating tool calls with ambiguous outcomes: class-specific advice lines, and identical retries inside the expiry window escalated to an ask. Default ON since 2026-10-16 (was opt-in). User layer only.Interactive basics
progress-rebuildenabled (true), max-lines (120), include-verified (true)Restores a structured progress brief into the first model request after a successful compaction. Default ON since 2026-10-16 (was ship-dark).Interactive basics
cc-turn-rulesenabled (true), max-result-bytes (200000), regex-cache-size (64), judged.enabled (false), repeat-reminder.enabled (false), repeat-reminder.thresholds ([3,5,8]), repeat-reminder.include/exclude ([]), repeat-reminder.arguments-preview-chars (500)Turn rules: Cursor-plugin rules carrying a trigger regex are injected as an advisory reminder when it matches. On by default with zero effect until such a rule exists. The namespace also carries the built-in repeat-reminder (default off): when the top-level agent makes the same tool call with identical arguments several times in a row, it fires an advisory reminder at each of the default thresholds ([3,5,8]), reset by the next user prompt. User layer only.Plugins
cc-advisorenabled (false), alias (haiku), budget (2), immune-turns (3), session-cap (24), severities (all three), subagents (off)Opt-in advisor watchdog: a second model reviews every completed turn. User layer only.Interactive basics
cc-lsp-on-writeenabled (false), server-name (serena), timeout-ms (1500), max-diagnostics (8), min-severity (warning), tool-namesOpt-in LSP diagnostics appended to edit/write/NotebookEdit results, pulled from a running serena MCP server. User layer only.Interactive basics
cc-secretsextraPatterns, redactCrusherStoreExtra regex sources for the one-way secret redaction applied to /export output and the context-crusher store.Slash commands
cc-foreign-rulesdisabledOpts out of harvesting other agents' rule files (cline/windsurf/copilot) from the session cwd into a cc:foreign-rules prompt section.Compatibility
cc-model-cyclingcycleOrderThe alias list Ctrl+P / Shift+Ctrl+P cycles through in the TUI.Model routing
config-snapshotconfig-snapshot.enabled (true)Writes one JSONL row per session construction to <dshHome>/config-snapshot/<encodedId>.jsonl (dsh-cc version, harness version, preset id, plugin inventory); observability only — the async writer is fail-open and never blocks session creation.—

Three more namespaces exist only inside the VWA agent preset:

NamespaceKeys (defaults)What it tunes
vwa-argumentsemantic-enabled (false), review-alias (sonnet)Lane-2 argument tools: semantic-enabled gates whether the lane-2 tools mount at all (they mount only when true); review-alias is the model alias used for the reviewer.
vwa-evidenceenabled (true inside the vwa preset), nudge-budget (4)Evidence tooling for the VWA project, including its nudge budget.
vwa-presetselectedDefault (absent)The saved default agent preset targeted by /preset; absent means the deployment default cc.

See also ​

dsh-cc is not Claude Code and is not a wrapper around Claude Code. Not affiliated with or endorsed by Anthropic.