Skip to content

feat(claude): expose programs.claude.settings.autoMode configuration#838

Merged
JacobPEvans-personal merged 2 commits into
mainfrom
feat/automode-options
May 30, 2026
Merged

feat(claude): expose programs.claude.settings.autoMode configuration#838
JacobPEvans-personal merged 2 commits into
mainfrom
feat/automode-options

Conversation

@JacobPEvans-personal
Copy link
Copy Markdown
Member

Summary

permissions.defaultMode = "auto" (already the default here) turns on Anthropic's auto-mode classifier. Out of the box the classifier trusts only the working directory and the active repo's configured remotes; cross-repo, cross-org, cloud, and homelab operations get blocked. The cure is configuring autoMode.{environment,allow,soft_deny,hard_deny} at top-level in settings.json. Reference: https://code.claude.com/docs/en/auto-mode-config.

Add typed sub-options under programs.claude.settings.autoMode:

  • environment — trusted-infrastructure prose (highest leverage)
  • allow — exceptions to soft_deny rules
  • soft_deny — destructive actions user intent can clear
  • hard_deny — unconditional blocks

Each defaults to [ "$defaults" ] (inherit built-ins). Consumers extend by adding prose entries; $defaults is spliced in at its position.

modules/claude/settings.nix filters sub-fields exactly equal to [ "$defaults" ] at write time — semantically a no-op since the classifier already uses defaults when a field is unset — so the generated settings.json stays minimal.

First consumer

JacobPEvans/nix-darwin#feat/automode-environment populates programs.claude.settings.autoMode.environment for the macbook-m4 host with the user's homelab trust context (GitHub orgs, AWS/Proxmox, MLX server, Doppler/Keychain/SOPS/Bitwarden, OTEL→Cribl→Splunk, self-hosted RunsOn runners, etc.).

Test plan

  • nix flake check passes
  • nix fmt no diff
  • Downstream rebuild: jq '.autoMode.environment' ~/.claude/settings.json returns populated list; claude auto-mode config shows entries; claude auto-mode critique AI review surfaces ambiguity

🤖 Generated with Claude Code

…block

Setting permissions.defaultMode = "auto" turns on Anthropic's auto-mode
classifier. Out of the box it trusts only the working directory and the
active repo's configured remotes — cross-repo, cross-org, cloud, and
homelab operations get blocked as potential exfiltration. The fix is
populating autoMode.environment with prose describing the user's
trusted infrastructure.

Reference: https://code.claude.com/docs/en/auto-mode-config

Add typed sub-options under programs.claude.settings.autoMode:
* environment — trusted-infrastructure entries (highest leverage)
* allow — exceptions to soft_deny rules
* soft_deny — destructive actions user intent can clear
* hard_deny — unconditional blocks

Each defaults to [ "$defaults" ] so the classifier inherits Anthropic's
built-in entries. Consumers extend by adding entries before/after
"$defaults" or replace entirely by omitting "$defaults".

modules/claude/settings.nix filters sub-fields exactly equal to
[ "$defaults" ] at write time — semantically a no-op since the
classifier uses defaults when a field is unset — so the generated
settings.json stays minimal.

Consumers (nix-darwin host configs, etc.) populate
programs.claude.settings.autoMode.environment with their homelab /
org / cloud trust context. The classifier then stops blocking routine
operations against that infrastructure.

Assisted-by: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces autoMode configuration options for the Claude module, enabling users to configure trusted infrastructure, allowlists, and deny rules for the auto-mode classifier. It also includes logic to keep the generated settings.json minimal by filtering out fields that only contain default values. Feedback was provided regarding the formatting of documentation strings to prevent inconsistent rendering of hyphenated words across line breaks.

Comment thread modules/claude/options-settings.nix Outdated
@JacobPEvans-personal
Copy link
Copy Markdown
Member Author

@claude review this PR

@JacobPEvans-personal
Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Address PR review feedback (Gemini): "natural-language" was split
mid-word in the autoMode.environment description. Compound hyphenation
across lines renders inconsistently in generated docs (e.g. man pages,
HTML). Rewrap so the compound stays whole.

Assisted-by: Claude <noreply@anthropic.com>
JacobPEvans-personal added a commit to dryvist/nix-darwin that referenced this pull request May 25, 2026
Rebase against main regenerated flake.lock and bumped nix-ai to its
post-PR-838 main, which removed the autoMode option that this PR
depends on. Restore the pin to fa9680a (HEAD of JacobPEvans/nix-ai
feat/automode-options branch) so this PR is buildable until
dryvist/nix-ai#838 merges.

Dependency: blocks on dryvist/nix-ai#838 merging first.

Assisted-by: Claude <noreply@anthropic.com>
@JacobPEvans-personal JacobPEvans-personal merged commit 54a0492 into main May 30, 2026
12 checks passed
@JacobPEvans-personal JacobPEvans-personal deleted the feat/automode-options branch May 30, 2026 15:46
JacobPEvans-personal added a commit to dryvist/nix-claude-code that referenced this pull request May 30, 2026
Consolidate Claude Code ownership in nix-claude-code so this repo becomes
the canonical home for the full `programs.claude.*` home-manager module.
Previously the option set lived in nix-ai (which also installs Gemini,
Codex, and Copilot); separation of concerns argues for the
Claude-specific schema to live here.

What's new in this PR:

- Full option set: `apiKeyHelper`, `teammateMode`, `autoUpdatesChannel`,
  `showTurnDuration`, `remoteControlAtStartup`, `trustedProjectDirs`,
  `model`, `effortLevel`, `attribution`, `plugins.{marketplaces,enabled,
  allowRuntimeInstall}`, `commands/agents/skills/rules.{fromFlakeInputs,
  local,…}`, the typed per-event `hooks.*`, `mcpServers`,
  `settings.{alwaysThinkingEnabled,cleanupPeriodDays,
  skillListingBudgetFraction,skillOverrides,permissions,
  additionalDirectories,env,schemaUrl,sandbox}`, `features.*`,
  `orphanCleanup.extraComponentDirs`, `latest.launchdLabel`,
  `statusline.script`.
- `lib.ci.claudeSettingsJson` shim for byte-equivalence tests.
- `lib.claudeRegistry` (toClaudeMarketplaceFormat / mkKnownMarketplaces).
- `lib.marketplaceCatalog` (the canonical marketplace list).
- `lib.marketplaceOverrides` (synthetic marketplace derivations:
  browser-use, fabric, cribl-pack-validator, jacobpevans).
- Vendored hook scripts (`last-output.sh`, `marketplace-refresh.sh`).
- Activation scripts in `modules/scripts/` (claude-json-merge,
  cleanup-*, verify-cache-integrity, merge-json-settings,
  claude-latest-install).
- API-key helper (`bws_helper.py` + `get-api-key.py`) wired as an
  opt-in shell wrapper with its Python deps in `runtimeInputs`.
- Synthetic `programs-claude-eval` flake check that exercises a
  minimal home-manager activation with `programs.claude.enable = true`.

Schema renames (`mkRenamedOptionModule` wired for back-compat):

- `programs.claude.enabledPlugins` → `programs.claude.plugins.enabled`
- `programs.claude.marketplaces` → `programs.claude.plugins.marketplaces`
- `programs.claude.statusLine.enable` → `programs.claude.statusline.enable`
- `programs.claude.statusLine.script` → `programs.claude.statusline.script`
- `programs.claude.hooks.extraHooks` → `programs.claude.settings.hooks`

`hooks.captureSessionOutput` and `hooks.refreshMarketplaces` toggles are
preserved and now auto-wire to the typed `postToolUse` / `sessionStart`
slots via `lib.mkDefault`. `programs.claude.defaultMode` remains the
canonical source of truth; `settings.permissions.defaultMode` wins only
when explicitly set (conflict #4).

`orphan-cleanup.nix` no longer hard-codes `~/.gemini/commands`; consumers
opt into extra cleanup dirs via `programs.claude.orphanCleanup.extraComponentDirs`.

Dependencies and follow-ups:

- DEPENDS ON: dryvist/nix-ai#838 (typed autoMode.environment) landing
  upstream. nix-claude-code already has `autoMode` on main, so no
  additional copy needed in this PR.
- FOLLOW-UP: nix-ai PR3 will delegate `programs.claude.*` to this module
  via imports.
- FOLLOW-UP: nix-darwin PR4 adopts the input and closes
  dryvist/nix-darwin#1141.

Refs: dryvist/nix-ai#838, dryvist/nix-darwin#1141

Assisted-by: Claude <noreply@anthropic.com>
JacobPEvans-personal added a commit to dryvist/nix-darwin that referenced this pull request May 30, 2026
* feat(claude): adopt nix-claude-code module

Add dryvist/nix-claude-code as a direct flake input and route
nix-ai's nix-claude-code instance through it via `follows`, so a
single Renovate PR cascades through both consumers.

Changes:
- flake.nix: add nix-claude-code input with follows for nixpkgs,
  home-manager, ai-assistant-instructions, claude-code-plugins,
  jacobpevans-cc-plugins; channel nix-ai.inputs.nix-claude-code
  through it
- flake.lock: pin nix-claude-code at PR2's merge SHA
  (5276fb566c8cf8078f808d8dbd5923bcd0e00c1a)
- lib/user-config.nix: drop unused `ai.claudeSchemaUrl` (the schema
  URL is now embedded in nix-claude-code's lib/to-settings-json.nix)
- renovate.json5: add dryvist/nix-claude-code to the ai-tools group
  with minimumReleaseAge=1 day so nix-ai leads, nix-darwin trails

Deferred to follow-up:
- The 10-server `mcpServers = {...}` block + splunk config in
  `hosts/macbook-m4/home.nix:62-95` is user-host opinion (which MCP
  servers this Mac wants disabled). Plan suggested moving it to
  nix-ai's MCP catalog, but that's a separate cross-repo change and
  the existing block continues to work through nix-claude-code's
  `mkRenamedOptionModule` shims.

Verified end-to-end: `darwin-rebuild build --flake .#jevans-mbp
--override-input nix-ai <PR3 branch>` succeeds with this flake.nix
+ PR3 merged content. Once PR3 merges, this branch's flake.lock
will also need a nix-ai bump (manual step before merge).

Refs: dryvist/nix-claude-code#26, dryvist/nix-ai#838, dryvist/nix-ai#851

Assisted-by: Claude <noreply@anthropic.com>

* chore(flake): bump nix-ai to PR3 merge

nix-ai PR #851 (refactor/delegate-claude-to-nix-claude-code) merged at
20fbd459106a. Bumping the lock to that SHA picks up the delegation so
nix-darwin now consumes programs.claude.* via nix-claude-code through
nix-ai's `imports`.

Resolves the prior `inputs.nix-claude-code.follows` warning on
non-existent input — PR3 added the input to nix-ai.

Refs: dryvist/nix-ai#851

Assisted-by: Claude <noreply@anthropic.com>

* fix(user-config): restore ai.claudeSchemaUrl

nix-ai/modules/default.nix:106 still references userConfig.ai.claudeSchemaUrl
in its validateClaudeSettings activation hook. The plan called for deleting
this on both sides, but PR3 didn't drop the nix-ai consumer — so deleting
just the nix-darwin definition breaks CI's Nix Build with "attribute 'ai'
missing".

Restoring the attribute. Delete in a follow-up after nix-ai stops
consuming it (move to inline constant or read from nix-claude-code.lib).

Assisted-by: Claude <noreply@anthropic.com>

* chore(deps): align flake.nix + renovate owners with current GitHub state

Per the JacobPEvans → dryvist org migration, four nix-darwin flake
inputs and their renovate matchPackageNames now use the canonical
dryvist owner. The previous JacobPEvans/* URLs worked via GitHub's
repo-rename redirect, but the literal owner string is what Renovate
matches — keeping flake and renovate in lockstep avoids mismatch.

Changes:
- flake.nix: `github:JacobPEvans/{ai-assistant-instructions,
  claude-code-plugins,nix-ai,nix-home}` → `github:dryvist/...`
- renovate.json5: matchPackageNames updated to match
- renovate.json5: extends `local>JacobPEvans/.github:renovate-presets`
  → `local>JacobPEvans-personal/.github:renovate-presets`
  (the .github repo stayed with the renamed personal account, not the
  dryvist org; verified `gh api repos/dryvist/.github/contents/renovate-presets.json` returns 404)
- renovate.json5: `@JacobPEvans` assignees/reviewers
  → `@JacobPEvans-personal` (bare JacobPEvans login is now 404; mentions
  don't follow GitHub's repo-rename redirect)
- flake.lock: re-resolved against dryvist URLs; nix-ai/nix-home SHAs
  unchanged (same commit, different owner), ai-assistant-instructions
  and claude-code-plugins picked up fresher SHAs

Assisted-by: Claude <noreply@anthropic.com>

* feat(user-config): drop ai.claudeSchemaUrl (single source in nix-claude-code)

The schemastore URL is a hard-coded constant that will never change;
the canonical home is dryvist/nix-claude-code's lib/to-settings-json.nix
where it's embedded as the generated settings.json's "$schema" field.

nix-ai stopped consuming `userConfig.ai.claudeSchemaUrl` in
dryvist/nix-ai#852 (merged), so deleting this
nix-darwin definition is now safe — the validateClaudeSettings
activation hook in nix-ai inlines the same constant.

Also bumps nix-ai input to the PR #852 merge SHA so the eval finds the
inlined URL constant.

Refs: dryvist/nix-ai#852

Assisted-by: Claude <noreply@anthropic.com>

* refactor(flake): keep nix-darwin AI-free; consolidate renovate policy

Three corrections to the migration's adoption shape, from review feedback:

1. **Drop nix-claude-code top-level input.** nix-darwin should not pull
   AI flakes directly — nix-ai is the only AI flake nix-darwin imports.
   nix-claude-code now flows transitively via nix-ai's input chain
   (dryvist/nix-ai#851 made nix-ai consume nix-claude-code), so this
   was strictly a leakage of Claude config concerns into nix-darwin's
   top-level. Removes the unhelpful "marketplace edge-pins cascade"
   comment too — that was the wrong design.

2. **Renovate: use dryvist as canonical org.** Switch
   `local>JacobPEvans-personal/.github:renovate-presets` →
   `local>dryvist/.github` (dryvist/.github/renovate.json extends the
   JacobPEvans preset chain via redirect; canonical owner is dryvist).

3. **Renovate: split internal-flakes off external groups.** dryvist
   and JacobPEvans-personal flakes merge immediately — never wait for
   release age on first-party repos. Critical-infrastructure group
   keeps its 2-day delay for nixpkgs-25.11-darwin Hydra cache lag;
   ai-tools-external keeps daily updates for anthropics/obra. New
   internal-flakes rule sits LAST so its minimumReleaseAge=0 +
   automerge=true override the prior groups for dryvist/**,
   JacobPEvans/**, JacobPEvans-personal/** matches.

Also drops the Claude config block (playwright disable + 10 MCP
server disables + splunk MCP definition) from
hosts/macbook-m4/home.nix. These moved to nix-ai/modules/claude-config.nix
in dryvist/nix-ai#853 — Claude config doesn't belong in nix-darwin.
home.nix shrinks 12406 → 10886 bytes, below the 12KB file-size CI limit.

nix-ai input bumped to 110a4a6 (PR #853 merge SHA) so the new MCP +
playwright defaults take effect.

Refs: dryvist/nix-ai#853

Assisted-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant