Skip to content

feat(claude-code): add managed_settings input for policy delivery via /etc/claude-code#863

Open
morganl-ant wants to merge 4 commits intocoder:mainfrom
morganl-ant:anthropic/managed-settings
Open

feat(claude-code): add managed_settings input for policy delivery via /etc/claude-code#863
morganl-ant wants to merge 4 commits intocoder:mainfrom
morganl-ant:anthropic/managed-settings

Conversation

@morganl-ant
Copy link
Copy Markdown
Contributor

Problem

The module configures Claude Code's permission posture by reaching around the permission system rather than through it:

  • scripts/install.sh writes bypassPermissionsModeAccepted, autoModeAccepted, and primaryApiKey directly into the user-writable ~/.claude.json. Any process in the workspace can read the API key or flip the acceptance flags back.
  • scripts/start.sh adds --dangerously-skip-permissions to every task launch, even when the template author set an explicit permission_mode. The README has to carry a security warning telling people the module bypasses permission checks.
  • permission_mode, allowed_tools, and disallowed_tools each plumb through a different ad-hoc path (CLI flag, coder subcommand) instead of a single policy surface.

Change

Add a managed_settings input that renders to /etc/claude-code/managed-settings.d/10-coder.json. Claude Code reads that drop-in directory at startup with the highest configuration precedence (above ~/.claude/settings.json and project settings), so template authors get an admin-controlled policy file that users inside the workspace cannot override. The mechanism is a local file read with no API call, so it works identically for the Anthropic API, AWS Bedrock, Google Vertex AI, and AI Bridge / AI Gateway.

managed_settings = {
  permissions = {
    defaultMode                  = "acceptEdits"
    disableBypassPermissionsMode = "disable"
    deny                         = ["Bash(curl:*)", "WebFetch"]
  }
}

Supporting changes:

  • install.sh writes the policy file (root-owned, 0644) and stops writing bypassPermissionsModeAccepted, autoModeAccepted, and primaryApiKey into ~/.claude.json. The API key is already exported via coder_env as CLAUDE_API_KEY; duplicating it on disk is unnecessary. hasCompletedOnboarding stays because there is no env-var alternative for it.
  • start.sh only adds --dangerously-skip-permissions for tasks when no explicit permission_mode is set (same fix as fix(claude-code): don't pass --dangerously-skip-permissions in auto mode #846; included here so this PR is self-contained, happy to drop if fix(claude-code): don't pass --dangerously-skip-permissions in auto mode #846 lands first).
  • permission_mode, allowed_tools, and disallowed_tools are marked deprecated and shimmed into managed_settings.permissions for one release when managed_settings is not provided.
  • README security warning rewritten to point at the policy mechanism instead of telling people the module is unsafe by design.

Relationship to #861

#861 strips this module to install-and-configure and removes permission_mode / allowed_tools / disallowed_tools outright. managed_settings is the natural replacement for those: it is install-time (survives the start.sh removal), it covers everything the dropped variables did plus hooks, env, model, apiKeyHelper, and the rest of the settings schema, and it does not require the module to know anything about how Claude is launched. If #861 lands first I will rebase this on top and drop the deprecation shim and the start.sh hunk.

Validation

  • terraform fmt / terraform validate clean
  • New tests: claude-managed-settings-written, claude-managed-settings-legacy-shim, claude-no-policy-keys-in-claudejson, plus an assertion in claude-auto-permission-mode that --dangerously-skip-permissions is absent when a mode is set
  • Manually verified /etc/claude-code/managed-settings.d/*.json precedence in the Claude Code CLI source

Closes #818. Relates to #284, #846, #861.

Disclosure: I work at Anthropic on the Claude Code team. Happy to adjust scope or split this further if that is easier to review.

@morganl-ant morganl-ant marked this pull request as ready for review April 22, 2026 20:57
@matifali matifali added the version:minor Add to PRs requiring a minor version upgrade label Apr 23, 2026
@matifali
Copy link
Copy Markdown
Member

This would need rebasing after #861 is merged. Thanks for your contribution.

… /etc/claude-code

Re-authored on top of the post-coder#861 install-only module.

Adds a managed_settings variable that the install script writes to
/etc/claude-code/managed-settings.d/10-coder.json. Claude Code reads
this drop-in directory at startup with the highest configuration
precedence, so template authors get an admin-controlled policy file
that users inside the workspace cannot override. The mechanism is a
local file read with no API call, so it works identically for the
Anthropic API, AWS Bedrock, Google Vertex AI, and AI Gateway.

Compared to the original PR against v4.x, this drops the deprecation
shim for permission_mode/allowed_tools/disallowed_tools (those vars
are gone in v5) and the start.sh changes (start.sh is gone). The
~/.claude.json policy-key removal is also dropped from this PR scope
since the surrounding configure_standalone_mode logic changed
substantially in coder#861; can revisit separately if wanted.
@morganl-ant morganl-ant force-pushed the anthropic/managed-settings branch from 913224b to 4b83931 Compare April 27, 2026 23:26
@morganl-ant
Copy link
Copy Markdown
Contributor Author

Rebased onto post-#861 main. Scope is now smaller: just the managed_settings input writing /etc/claude-code/managed-settings.d/10-coder.json. The deprecation shim and ~/.claude.json key removal from the original PR are no longer needed since #861 dropped those vars and writes.

…ude.json

These flags pre-accept the permission-mode confirmation dialogs in a
user-writable file. Permission posture is the managed_settings input's
job (delivered via /etc/claude-code/managed-settings.d/, root-owned,
not user-overridable). Onboarding-bypass keys (hasCompletedOnboarding,
hasAcknowledgedCostThreshold, project trust) stay since there is no
managed-settings equivalent for those.
Copy link
Copy Markdown
Member

@matifali matifali left a comment

Choose a reason for hiding this comment

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

I like the flexibility this PR adds. I think this supercedes #866 and covers the use case.

Comment on lines -161 to -162
.autoModeAccepted = true |
.bypassPermissionsModeAccepted = true |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why did we remove this? AFAIK, these were needed for skipping the welcome wizard.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think the reason they are removing them is because you are able to set these same behaviors through managed settings. Which is probably better than us pre-seeding it for all users in this hacky unobservable way.

Although I think we should probably test this a bit more before committing since we want to make sure this won't introduce too big of a behavioral shift in the module for users who have this running in a task workflow.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This serves the standalone mode, which we need to set anyway when a user provides a workdir. I don't see how that would work if we remove this. I am all good if we can do it more systematically. But if a user starts Claude via coder_script in a specific workdir, they should not be greeted with a config wizard.

Copy link
Copy Markdown
Collaborator

@DevelopmentCats DevelopmentCats Apr 28, 2026

Choose a reason for hiding this comment

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

You know I think you are right here. These are mostly just for the dialogue boxes unless they get suppressed now somehow.

Comment on lines +293 to +297
managed_settings = {
permissions = {
defaultMode = "acceptEdits"
disableBypassPermissionsMode = "disable"
deny = ["Bash(rm -rf*)"]
Copy link
Copy Markdown
Collaborator

@DevelopmentCats DevelopmentCats Apr 28, 2026

Choose a reason for hiding this comment

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

@matifali for reference this is how you would pass similar behavior through managed settings rather than pre-seeding it.

So essentially someone could set defaultMode=auto and same with the rest for claude-code running for tasks and this would work over even the settings.json

@DevelopmentCats
Copy link
Copy Markdown
Collaborator

Everything looks good here for me @morganl-ant I will go ahead and test this and approve it from my end once done.

@DevelopmentCats
Copy link
Copy Markdown
Collaborator

@morganl-ant @matifali

I have tested this and it works properly with agentapi/tasks provided you do the following in the new module

  claude_managed_settings = {
    permissions = {
      defaultMode = "bypassPermissions"
      allow       = ["*"]
      deny        = []
    }
    # Skip the first-run theme picker / onboarding prompts.
    theme                  = "dark"
    hasCompletedOnboarding = true
  }

@matifali
Copy link
Copy Markdown
Member

@DevelopmentCats, I guess we can safely remove (deprecate) the workdir input then.

@DevelopmentCats
Copy link
Copy Markdown
Collaborator

@DevelopmentCats, I guess we can safely remove (deprecate) the workdir input then.

Yeah we definitely can if the only thing that was holding us back was project level configs.

Resolve conflict in main.test.ts: keep both managed-settings tests
(from this PR) and telemetry tests (from coder#862/main).
@DevelopmentCats
Copy link
Copy Markdown
Collaborator

@matifali I think we are good to merge this. If we decide later that we want to add back the part where we auto fill the config info the managed settings to bypass the prompts by default, but its probably fine to let the users or administrator decide what they need for the managed settings in the module to make it perform how they'd like. Especially with the move to agents.

@DevelopmentCats DevelopmentCats added version:minor Add to PRs requiring a minor version upgrade and removed version:minor Add to PRs requiring a minor version upgrade labels Apr 29, 2026
@matifali
Copy link
Copy Markdown
Member

@DevelopmentCats Can you test the coder_app example use case? If it can start Claude without user needing to do anything I am fine merging this.

@DevelopmentCats
Copy link
Copy Markdown
Collaborator

@DevelopmentCats Can you test the coder_app example use case? If it can start Claude without user needing to do anything I am fine merging this.

Yeah I will test and provide a picture

@matifali
Copy link
Copy Markdown
Member

@DevelopmentCats can also update example with required settings (if needed)

@DevelopmentCats
Copy link
Copy Markdown
Collaborator

@morganl-ant Is there a way to suppress either of these Claude-Code CLI alerts through a setting that can be done through managed_settings?

image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

version:minor Add to PRs requiring a minor version upgrade

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claude-code: Add 'auto' and 'dontAsk' to permission_mode validation

3 participants