From 066cafdf8860a19c97637f746b57452b9124655c Mon Sep 17 00:00:00 2001 From: Arun Sekhar Date: Fri, 29 May 2026 16:05:12 -0700 Subject: [PATCH] Flip postprovision default: don't write .vscode/settings.json unless CLAUDE_WRITE_VSCODE_SETTINGS=1 (#28) Follow-up to #25 / #26. The starter's primary consumers (Anthropic SDK users, Claude Code CLI users, OpenAI-compatible-client users) don't need .vscode/settings.json; only the Anthropic Claude Code VS Code extension does, and that's a niche. azd up writing into the workspace by default is surprising for the majority. Flip it: skip by default, opt in with CLAUDE_WRITE_VSCODE_SETTINGS=1 (or -WriteVsCodeSettings / --write-vscode-settings).\n\nKeeps CLAUDE_SKIP_VSCODE_SETTINGS, -SkipVsCodeSettings, and --skip-vscode-settings as deprecated no-ops so anyone who already set them after #26 doesn't break. Skipping is the default now anyway.\n\nUpdated README post-deploy section (step 2 marked Opt-in, lead-in line no longer claims the hook writes the file), copilot-instructions env-var table, and SKILL.md MODIFY table. --- .github/copilot-instructions.md | 2 +- README.md | 4 +-- scripts/configure-claude-code.ps1 | 42 +++++++++++++++++++++---------- scripts/configure-claude-code.sh | 41 +++++++++++++++++++----------- skills/claude-on-foundry/SKILL.md | 2 +- 5 files changed, 60 insertions(+), 31 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3c92356..6af9f8e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -30,7 +30,7 @@ This is the **Claude on Foundry Starter Kit** ([`Azure-Samples/claude`](https:// | `CLAUDE_MODEL_NAME` | no | `claude-sonnet-4-6` | **Legacy** single-deployment fallback (only used when all three `CLAUDE_*_MODEL` are empty) | | `ASSIGN_RBAC` | no | `false` | `true` grants Foundry User + Foundry Project Manager to `AZURE_PRINCIPAL_ID` | | `CLAUDE_CODE_AUTO_INSTALL` | no | `false` | `true` installs the Claude Code CLI in the postprovision hook | -| `CLAUDE_SKIP_VSCODE_SETTINGS` | no | `false` | `true` skips the `.vscode/settings.json` write in the postprovision hook (use when you don't have the Claude Code VS Code extension) | +| `CLAUDE_WRITE_VSCODE_SETTINGS` | no | `false` | `true` opts in to having the postprovision hook write `.vscode/settings.json` for the [Anthropic Claude Code VS Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code). Default skips it — the CLI / SDK don't need workspace settings. | Always set vars with `azd env set ` from inside the chosen variant folder. diff --git a/README.md b/README.md index 6e976e7..f01705b 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ azd up > **Want just one family?** Set only that one (e.g. just `CLAUDE_OPUS_MODEL`) and leave the others unset. Want to override capacity per family? Set `CLAUDE_HAIKU_CAPACITY` / `CLAUDE_SONNET_CAPACITY` / `CLAUDE_OPUS_CAPACITY` (TPM ÷ 1000, default 25 each). See [Choosing which models to deploy](#choosing-which-models-to-deploy). -`azd up` provisions Foundry + the Claude deployment, then a **postprovision** hook ([`scripts/configure-claude-code.ps1`](./scripts/configure-claude-code.ps1)) writes a `claude-code.env.ps1` / `claude-code.env.sh` activator at the repo root and a `.vscode/settings.json` for the Claude Code VS Code extension. See [Claude Code post-deploy setup](#claude-code-post-deploy-setup) for details. +`azd up` provisions Foundry + the Claude deployment, then a **postprovision** hook ([`scripts/configure-claude-code.ps1`](./scripts/configure-claude-code.ps1)) writes a `claude-code.env.ps1` / `claude-code.env.sh` activator at the repo root and pins a workspace default via `.claude/settings.json`. See [Claude Code post-deploy setup](#claude-code-post-deploy-setup) for details. ### Use Claude Code @@ -236,7 +236,7 @@ After `azd up` succeeds, the **postprovision** hook ([`scripts/configure-claude- - `ANTHROPIC_FOUNDRY_RESOURCE=` - One `ANTHROPIC_DEFAULT__MODEL=` per deployed family (`HAIKU` / `SONNET` / `OPUS`). Only the families you actually deployed get a line. - **`AZURE_CONFIG_DIR=/.azure-cli`** — scopes `az login` (and `azd`) to this workspace only. See [Workspace-scoped `az login`](#workspace-scoped-az-login) below. -2. Writes (or merges into) `.vscode/settings.json` with `claudeCode.environmentVariables` (the array-of-`{name,value}` schema the extension actually reads — the display name in the Settings UI is *"Claude Code: Environment Variables"*) and `claudeCode.disableLoginPrompt: true` so the [Claude Code VS Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) skips the Anthropic-account login and uses your Foundry deployment via Entra ID. It also sets `terminal.integrated.env.{windows,linux,osx}.AZURE_CONFIG_DIR` so every terminal VS Code spawns in this workspace inherits the scoped Azure config automatically — you don't even have to source the activator first. **Not using the Claude Code extension?** Opt out before `azd up` with `azd env set CLAUDE_SKIP_VSCODE_SETTINGS 1` (or pass `-SkipVsCodeSettings` / `--skip-vscode-settings` when running the script standalone) and the hook leaves `.vscode/settings.json` alone. The activator at step 1 still works for sourced shells. +2. **(Opt-in)** Writes (or merges into) `.vscode/settings.json` with `claudeCode.environmentVariables` (the array-of-`{name,value}` schema the extension actually reads — the display name in the Settings UI is *"Claude Code: Environment Variables"*) and `claudeCode.disableLoginPrompt: true` so the [Claude Code VS Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) skips the Anthropic-account login and uses your Foundry deployment via Entra ID. It also sets `terminal.integrated.env.{windows,linux,osx}.AZURE_CONFIG_DIR` so every terminal VS Code spawns in this workspace inherits the scoped Azure config automatically — you don't even have to source the activator first. **This step only runs if you ask for it** — the hook leaves `.vscode/settings.json` alone by default since most users run Claude from the SDK, the Claude Code CLI (which only needs the activator at step 1), or another OpenAI-compatible client. **Using the Claude Code extension?** Opt in before `azd up` with `azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1` (or pass `-WriteVsCodeSettings` / `--write-vscode-settings` when running the script standalone). 3. Writes (or merges into) `.claude/settings.json` at the repo root with `{ "model": "" }` pinned to a deployed family (sonnet > opus > haiku priority). This is the **workspace-level** Claude Code config and overrides whatever is in your user-global `~/.claude/settings.json` — so bare `claude` / `claude -p` resolves to a family you actually deployed, even if your global default points elsewhere. 4. Checks whether `claude` is on PATH. If not, prints the platform-appropriate one-liner install command. Set `CLAUDE_CODE_AUTO_INSTALL=true` *before* `azd up` to run [the official installer](https://claude.ai/install.ps1) automatically. diff --git a/scripts/configure-claude-code.ps1 b/scripts/configure-claude-code.ps1 index 80de74d..1fe8bd0 100644 --- a/scripts/configure-claude-code.ps1 +++ b/scripts/configure-claude-code.ps1 @@ -33,18 +33,25 @@ [CmdletBinding()] param( [string] $RepoRoot, + [switch] $WriteVsCodeSettings, + # Deprecated no-op: skipping is now the default. Kept so existing CI / docs + # that still pass -SkipVsCodeSettings don't break. [switch] $SkipVsCodeSettings ) $ErrorActionPreference = 'Stop' -# Env-var opt-out so the postprovision hook can honor it without having to -# pass -SkipVsCodeSettings on the command line: -# azd env set CLAUDE_SKIP_VSCODE_SETTINGS 1 -if (-not $SkipVsCodeSettings) { - $skipEnv = $env:CLAUDE_SKIP_VSCODE_SETTINGS - if ($skipEnv -and $skipEnv -match '^(1|true|yes|on)$') { - $SkipVsCodeSettings = $true +# .vscode/settings.json is opt-in. Most users of this starter call Claude from +# the Anthropic SDK, the Claude Code CLI (uses the activator at the repo root, +# no workspace settings needed), or another OpenAI-compatible client. Only +# users of the Anthropic Claude Code VS Code extension benefit from having +# claudeCode.* keys written into their workspace settings, so make them ask: +# azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1 +# (or pass -WriteVsCodeSettings when running the script standalone). +if (-not $WriteVsCodeSettings) { + $writeEnv = $env:CLAUDE_WRITE_VSCODE_SETTINGS + if ($writeEnv -and $writeEnv -match '^(1|true|yes|on)$') { + $WriteVsCodeSettings = $true } } @@ -185,10 +192,12 @@ Write-Host "Wrote activator: $ps1Path" Write-Host "Wrote activator: $shPath" # --------------------------------------------------------------------------- -# 2. Write / merge `.vscode/settings.json` for the Claude Code VS Code extension. +# 2. Optionally write / merge `.vscode/settings.json` for the Claude Code +# VS Code extension. Opt-in via CLAUDE_WRITE_VSCODE_SETTINGS=1 (or +# -WriteVsCodeSettings) since most users don't run that extension. # --------------------------------------------------------------------------- -if ($SkipVsCodeSettings) { - Write-Host "Skipping .vscode/settings.json (CLAUDE_SKIP_VSCODE_SETTINGS / -SkipVsCodeSettings set). The activator above still wires up sourced shells." +if (-not $WriteVsCodeSettings) { + Write-Host "Skipping .vscode/settings.json (opt-in). Set 'azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1' before 'azd up' to wire up the Anthropic Claude Code VS Code extension automatically. The activator above already works for sourced shells." } else { $vscodeDir = Join-Path $RepoRoot '.vscode' $settingsPath = Join-Path $vscodeDir 'settings.json' @@ -197,6 +206,7 @@ if ($SkipVsCodeSettings) { } $existing = [ordered]@{} + $parseFailed = $false if (Test-Path $settingsPath) { try { $raw = Get-Content -Raw -Path $settingsPath @@ -208,11 +218,11 @@ if ($SkipVsCodeSettings) { } } catch { Write-Host "WARNING: Could not parse existing $settingsPath ($($_.Exception.Message)). Leaving it untouched." -ForegroundColor Yellow - $SkipVsCodeSettings = $true + $parseFailed = $true } } - if (-not $SkipVsCodeSettings) { + if (-not $parseFailed) { # Use [ordered] hashtables per entry so name appears before value in # the rendered JSON (PSCustomObject hashtable iteration is unordered). $claudeEnv = @( @@ -372,6 +382,12 @@ Write-Host " claude" Write-Host "" Write-Host "Or in VS Code: install the 'Claude Code' extension" Write-Host "(https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code)" -Write-Host "— the .vscode/settings.json in this workspace already wires it up." +if ($WriteVsCodeSettings) { + Write-Host "— the .vscode/settings.json in this workspace already wires it up." +} else { + Write-Host "— then re-run 'azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1; azd provision'" + Write-Host " (or '. .\scripts\configure-claude-code.ps1 -WriteVsCodeSettings')" + Write-Host " to auto-wire the extension to this Foundry deployment." +} Write-Host "" exit 0 diff --git a/scripts/configure-claude-code.sh b/scripts/configure-claude-code.sh index ab697d3..f4c7c69 100644 --- a/scripts/configure-claude-code.sh +++ b/scripts/configure-claude-code.sh @@ -27,20 +27,27 @@ fail() { SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="${REPO_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" -# Skip flag from env. Prefer the CLAUDE_-namespaced name (matches the rest of -# the env-var contract); keep the un-prefixed name as a deprecated alias. -# Set via: azd env set CLAUDE_SKIP_VSCODE_SETTINGS 1 -SKIP_VSCODE_SETTINGS="${CLAUDE_SKIP_VSCODE_SETTINGS:-${SKIP_VSCODE_SETTINGS:-0}}" -case "$SKIP_VSCODE_SETTINGS" in - 1|true|TRUE|yes|YES|on|ON) SKIP_VSCODE_SETTINGS=1 ;; - *) SKIP_VSCODE_SETTINGS=0 ;; +# .vscode/settings.json is opt-in. Most users of this starter call Claude from +# the Anthropic SDK, the Claude Code CLI (uses the activator at the repo root, +# no workspace settings needed), or another OpenAI-compatible client. Only +# users of the Anthropic Claude Code VS Code extension benefit from having +# claudeCode.* keys written into their workspace settings, so make them ask: +# azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1 +# (or pass --write-vscode-settings when running the script standalone). +WRITE_VSCODE_SETTINGS="${CLAUDE_WRITE_VSCODE_SETTINGS:-0}" +case "$WRITE_VSCODE_SETTINGS" in + 1|true|TRUE|yes|YES|on|ON) WRITE_VSCODE_SETTINGS=1 ;; + *) WRITE_VSCODE_SETTINGS=0 ;; esac while [[ $# -gt 0 ]]; do case "$1" in - --repo-root) REPO_ROOT="$2"; shift 2 ;; - --repo-root=*) REPO_ROOT="${1#*=}"; shift ;; - --skip-vscode-settings) SKIP_VSCODE_SETTINGS=1; shift ;; + --repo-root) REPO_ROOT="$2"; shift 2 ;; + --repo-root=*) REPO_ROOT="${1#*=}"; shift ;; + --write-vscode-settings) WRITE_VSCODE_SETTINGS=1; shift ;; + # Deprecated no-op: skipping is now the default. Kept so existing CI + # / docs that still pass --skip-vscode-settings don't break. + --skip-vscode-settings) shift ;; *) fail 2 "Unknown argument: $1" ;; esac done @@ -161,8 +168,8 @@ for cand in python python3; do if command -v "$cand" >/dev/null 2>&1; then PYTHON_BIN="$cand"; break; fi done -if [ "${SKIP_VSCODE_SETTINGS:-}" = "1" ]; then - echo "Skipping .vscode/settings.json (CLAUDE_SKIP_VSCODE_SETTINGS / --skip-vscode-settings set). The activator above still wires up sourced shells." +if [ "${WRITE_VSCODE_SETTINGS:-}" != "1" ]; then + echo "Skipping .vscode/settings.json (opt-in). Set 'azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1' before 'azd up' to wire up the Anthropic Claude Code VS Code extension automatically. The activator above already works for sourced shells." elif [ -n "$PYTHON_BIN" ]; then VSCODE_DIR="$REPO_ROOT/.vscode" mkdir -p "$VSCODE_DIR" @@ -219,7 +226,7 @@ with open(path, 'w', encoding='utf-8') as f: f.write('\n') print(f"Wrote VS Code settings: {path}") PYEOF -elif [ "${SKIP_VSCODE_SETTINGS:-}" != "1" ]; then +elif [ "${WRITE_VSCODE_SETTINGS:-}" = "1" ]; then echo "WARNING: python not found on PATH; skipping .vscode/settings.json." fi @@ -334,6 +341,12 @@ echo " claude" echo "" echo "Or in VS Code: install the 'Claude Code' extension" echo "(https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code)" -echo "- the .vscode/settings.json in this workspace already wires it up." +if [ "${WRITE_VSCODE_SETTINGS:-}" = "1" ]; then + echo "- the .vscode/settings.json in this workspace already wires it up." +else + echo "- then re-run 'azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1; azd provision'" + echo " (or 'bash scripts/configure-claude-code.sh --write-vscode-settings')" + echo " to auto-wire the extension to this Foundry deployment." +fi echo "" exit 0 diff --git a/skills/claude-on-foundry/SKILL.md b/skills/claude-on-foundry/SKILL.md index 9b32a17..2a83883 100644 --- a/skills/claude-on-foundry/SKILL.md +++ b/skills/claude-on-foundry/SKILL.md @@ -170,7 +170,7 @@ claude # interactive REPL; try /status and /mode | **Switch region** | `azd env new ` in the same variant folder, then redo the [DEPLOY](#deploy--running-azd-up) flow. **Don't** try to mutate `AZURE_LOCATION` on an existing env — the account is region-stamped. | | **Switch variants (Bicep ↔ Terraform)** | They produce equivalent infra but with different `azd` env state. Create a new env in the other folder: `cd infra-terraform && azd env new && ...`. | | **Refresh Claude Code wiring** | `pwsh -File scripts/configure-claude-code.ps1` (or the `.sh` variant). Idempotent — runs without re-deploying. | -| **Skip the `.vscode/settings.json` write** | `azd env set CLAUDE_SKIP_VSCODE_SETTINGS 1` (then re-run `azd provision` or the configure script). For customers who don't use the Claude Code VS Code extension and don't want workspace settings touched. The activator at the repo root still wires up sourced shells. | +| **Wire up the Claude Code VS Code extension** | `azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1` (then re-run `azd provision` or the configure script). Opt-in because the activator + `.claude/settings.json` are enough for the CLI and SDK; only the [Anthropic Claude Code VS Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) needs `claudeCode.*` keys in workspace settings. | | **Convert to long-running auth** | Replace `Anthropic(auth_token=...)` with `AnthropicIdentity(azure_ad_token_provider=...)` from [`src/hello_claude_token_refresh.py`](../../src/hello_claude_token_refresh.py). | ---