claude-dev-env 8.54.9 → 8.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/bin/install.agents-home.test.mjs +2 -0
  2. package/bin/install.codex-rules.test.mjs +4 -0
  3. package/bin/install.cursor-rules.test.mjs +1 -0
  4. package/bin/install.mjs +19 -20
  5. package/bin/install.profile-settings.test.mjs +77 -0
  6. package/bin/install.profiles.test.mjs +2 -2
  7. package/bin/install.prune.test.mjs +2 -0
  8. package/bin/install.question-presentation.test.mjs +2 -0
  9. package/bin/install.settings-defaults.test.mjs +8 -3
  10. package/bin/install.shared-settings.test.mjs +1 -0
  11. package/bin/install.test.mjs +7 -3
  12. package/bin/install.transaction.test.mjs +14 -0
  13. package/bin/install.uninstall-transaction.test.mjs +4 -0
  14. package/bin/install.update-keeps-hooks.test.mjs +2 -0
  15. package/bin/merge_profile_settings.mjs +372 -0
  16. package/bin/merge_profile_settings.test.mjs +208 -0
  17. package/docs/templates/artifact-page/README.md +25 -0
  18. package/docs/templates/artifact-page/template.html +465 -0
  19. package/hooks/features/session-context.md +4 -1
  20. package/hooks/hooks.json +10 -0
  21. package/hooks/hooks_constants/artifact_template_pointer_constants.py +27 -0
  22. package/hooks/hooks_constants/test_artifact_template_pointer_constants.py +8 -0
  23. package/hooks/session/artifact_template_pointer.py +102 -0
  24. package/hooks/session/test_artifact_template_pointer.py +65 -0
  25. package/installable-surfaces.manifest.json +2 -1
  26. package/package.json +3 -2
  27. package/profile-settings/config/__init__.py +1 -0
  28. package/profile-settings/config/session_title_gate_constants.py +15 -0
  29. package/profile-settings/session_title_status_gate.py +77 -0
  30. package/profile-settings/test_session_title_status_gate.py +101 -0
  31. package/scripts/dev_env_scripts_constants/private_term_constants.py +8 -4
  32. package/scripts/private_term_scan.py +15 -3
  33. package/scripts/tests/test_private_term_scan.py +81 -8
@@ -50,6 +50,8 @@ const REVIEW_PROCEDURE_FILE_NAME = 'high.md';
50
50
  function runInstaller(homeDirectory, extraArguments, environmentOverrides = {}) {
51
51
  const installerEnvironment = {
52
52
  ...process.env,
53
+ CLAUDE_CONFIG_DIR: undefined,
54
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
53
55
  CDE_INSTALL_PSTACK: '0',
54
56
  CDE_INSTALL_USAGE_WRAPUP: '0',
55
57
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -56,6 +56,8 @@ function runInstaller(homeDirectory, extraArguments) {
56
56
  encoding: 'utf8',
57
57
  env: {
58
58
  ...process.env,
59
+ CLAUDE_CONFIG_DIR: undefined,
60
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
59
61
  CDE_INSTALL_PSTACK: '0',
60
62
  CDE_INSTALL_USAGE_WRAPUP: '0',
61
63
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -86,6 +88,8 @@ function scanTextWithProductionPiiScanner(scannedText) {
86
88
  encoding: 'utf8',
87
89
  env: {
88
90
  ...process.env,
91
+ CLAUDE_CONFIG_DIR: undefined,
92
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
89
93
  CDE_INSTALL_PSTACK: '0',
90
94
  CDE_INSTALL_USAGE_WRAPUP: '0',
91
95
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -31,6 +31,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
31
31
  encoding: 'utf8',
32
32
  env: {
33
33
  ...process.env,
34
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
34
35
  CDE_INSTALL_PSTACK: '0',
35
36
  CDE_INSTALL_USAGE_WRAPUP: '0',
36
37
  CDE_INSTALL_SUBAGENT_MODELS: '0',
package/bin/install.mjs CHANGED
@@ -29,6 +29,11 @@ import {
29
29
  mergeMissingSettingsDefaults,
30
30
  settingsDefaultsFromPackageSettings,
31
31
  } from './merge_settings_defaults.mjs';
32
+ import {
33
+ describeProfileSettingsOutcome,
34
+ hostConfigurationIndent,
35
+ mergeProfileSettings,
36
+ } from './merge_profile_settings.mjs';
32
37
  import {
33
38
  SKIPPED_SOURCE_ENTRY_NAMES,
34
39
  SKIPPED_SOURCE_FILE_EXTENSIONS,
@@ -1548,15 +1553,19 @@ function startEventFromHookGroupList(settings, eventType) {
1548
1553
  * @param {object} settings The parsed settings.json object (mutated in place).
1549
1554
  * @param {{hooks: object}} hooksConfig Parsed hooks.json.
1550
1555
  * @param {string} pluginRootDir Directory ${CLAUDE_PLUGIN_ROOT} resolves to
1551
- * (the installer's `~/.claude` root; home is its parent directory).
1556
+ * (the installer's managed root, `~/.claude` or a profile root).
1552
1557
  * @param {string} pythonCommand Interpreter command that replaces python3.
1553
1558
  * @param {string|null} [sharedSettingsRealPath] Resolved path of the settings
1554
1559
  * file being merged. Hooks of every root whose settings.json resolves to it
1555
1560
  * count as managed, so roots sharing one file keep one entry per script.
1561
+ * @param {string} [homeDirectory] The home directory `~/`, `$HOME`, and
1562
+ * `${HOME}` expand to. It is the user's home whatever root installs, so a
1563
+ * profile root under `~/.claude-profiles` expands `~/` the same way the
1564
+ * profile settings merge does.
1556
1565
  * @returns {number} Count of matcher groups merged.
1557
1566
  */
1558
1567
  export function mergeHooksIntoSettings(
1559
- settings, hooksConfig, pluginRootDir, pythonCommand, sharedSettingsRealPath = null,
1568
+ settings, hooksConfig, pluginRootDir, pythonCommand, sharedSettingsRealPath = null, homeDirectory = homedir(),
1560
1569
  ) {
1561
1570
  const managedHookRelativePaths = managedHookScriptRelativePaths(hooksConfig);
1562
1571
  const pluginRootForward = pluginRootDir.replace(/\\/g, '/');
@@ -1604,7 +1613,7 @@ export function mergeHooksIntoSettings(
1604
1613
  groupCount++;
1605
1614
  }
1606
1615
  }
1607
- expandHomeDirectoryTokensInSettings(settings, dirname(pluginRootDir));
1616
+ expandHomeDirectoryTokensInSettings(settings, homeDirectory);
1608
1617
  return groupCount;
1609
1618
  }
1610
1619
 
@@ -1883,23 +1892,6 @@ function stripRetiredHookEntries(settings, retiredHookRelativePaths, ownership)
1883
1892
  return removedCount;
1884
1893
  }
1885
1894
 
1886
- const DEFAULT_HOST_CONFIGURATION_INDENT = ' ';
1887
-
1888
- /**
1889
- * Read the indent the host configuration file already uses.
1890
- *
1891
- * ~/.claude/settings.json uses four spaces and ~/.codex/hooks.json uses two.
1892
- * Forcing four spaces would rewrite every line of one of those files. The first
1893
- * indented line supplies the indent, tabs included.
1894
- *
1895
- * @param {string} settingsText The host configuration file as it stands on disk.
1896
- * @returns {string} The indent one nesting level uses.
1897
- */
1898
- function hostConfigurationIndent(settingsText) {
1899
- const firstIndentedLine = /\n([ \t]+)\S/.exec(settingsText);
1900
- return firstIndentedLine ? firstIndentedLine[1] : DEFAULT_HOST_CONFIGURATION_INDENT;
1901
- }
1902
-
1903
1895
  /**
1904
1896
  * Remove every entry of one host hook configuration file that runs a retired
1905
1897
  * managed hook script, writing the file only when an entry left it.
@@ -3027,6 +3019,13 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
3027
3019
  if (settingsDefaultsMerge.addedKeys.length > 0) {
3028
3020
  console.log(` Settings: added ${settingsDefaultsMerge.addedKeys.join(', ')}`);
3029
3021
  }
3022
+ const mergeOutcome = mergeProfileSettings(
3023
+ [join(CLAUDE_HOME, SETTINGS_FILE_NAME)],
3024
+ { dryRun: false, homeDirectory: homedir() },
3025
+ );
3026
+ for (const eachLine of describeProfileSettingsOutcome(mergeOutcome, false)) {
3027
+ console.log(` Profile settings: ${eachLine}`);
3028
+ }
3030
3029
 
3031
3030
  const agentsHubSource = join(PACKAGE_ROOT, 'AGENTS.md');
3032
3031
  if (existsSync(agentsHubSource)) {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Installing twice into a profile root under ~/.claude-profiles leaves exactly
3
+ * one session-title gate Stop hook, that hook runs the gate under ~/.claude, and
4
+ * the second install leaves settings.json byte for byte.
5
+ */
6
+
7
+ import { test } from 'node:test';
8
+ import { strict as assert } from 'node:assert';
9
+ import { join } from 'node:path';
10
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { spawnSync } from 'node:child_process';
14
+
15
+ import { ALL_SESSION_TITLE_GATE_RELATIVE_PATHS, SESSION_TITLE_GATE_FILE_NAME } from './merge_profile_settings.mjs';
16
+
17
+ const INSTALLER_PATH = fileURLToPath(new URL('./install.mjs', import.meta.url));
18
+
19
+ function installEnvironment(homeDirectory) {
20
+ return {
21
+ ...process.env,
22
+ CLAUDE_CONFIG_DIR: undefined,
23
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
24
+ HOME: homeDirectory,
25
+ USERPROFILE: homeDirectory,
26
+ GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
27
+ CODEX_HOME: join(homeDirectory, '.codex'),
28
+ CDE_INSTALL_PSTACK: '0',
29
+ CDE_INSTALL_USAGE_WRAPUP: '0',
30
+ };
31
+ }
32
+
33
+ function runCoreInstall(homeDirectory, profileRoot) {
34
+ const installRun = spawnSync(
35
+ process.execPath,
36
+ [INSTALLER_PATH, '--only', 'core', '--target', profileRoot],
37
+ { encoding: 'utf8', env: installEnvironment(homeDirectory) },
38
+ );
39
+ assert.equal(installRun.status, 0, installRun.stdout + installRun.stderr);
40
+ }
41
+
42
+ function stopHookCommands(settingsPath) {
43
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
44
+ return (settings.hooks?.Stop ?? []).flatMap(
45
+ (eachGroup) => (eachGroup.hooks ?? []).map((eachHook) => eachHook.command.replace(/\\/g, '/')),
46
+ );
47
+ }
48
+
49
+ test('two installs into a profile root keep one gate Stop hook that runs the gate under ~/.claude', () => {
50
+ const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-profile-settings-'));
51
+ try {
52
+ writeFileSync(join(homeDirectory, '.gitconfig'), '');
53
+ const profileRoot = join(homeDirectory, '.claude-profiles', 'work');
54
+ mkdirSync(profileRoot, { recursive: true });
55
+ const settingsPath = join(profileRoot, 'settings.json');
56
+ const forwardSlashedHome = homeDirectory.replace(/\\/g, '/');
57
+ const expectedGateCommand = `python3 ${forwardSlashedHome}/.claude/${SESSION_TITLE_GATE_FILE_NAME}`;
58
+
59
+ runCoreInstall(homeDirectory, profileRoot);
60
+ const settingsBytesAfterFirstInstall = readFileSync(settingsPath);
61
+ runCoreInstall(homeDirectory, profileRoot);
62
+
63
+ assert.deepEqual(readFileSync(settingsPath), settingsBytesAfterFirstInstall, 'second install leaves settings.json byte for byte');
64
+
65
+ const allGateCommands = stopHookCommands(settingsPath)
66
+ .filter((eachCommand) => eachCommand.includes(SESSION_TITLE_GATE_FILE_NAME));
67
+ assert.deepEqual(allGateCommands, [expectedGateCommand]);
68
+ for (const eachRelativePath of ALL_SESSION_TITLE_GATE_RELATIVE_PATHS) {
69
+ assert.ok(
70
+ existsSync(join(homeDirectory, '.claude', eachRelativePath)),
71
+ `${eachRelativePath} is installed under ~/.claude`,
72
+ );
73
+ }
74
+ } finally {
75
+ rmSync(homeDirectory, { recursive: true, force: true });
76
+ }
77
+ });
@@ -235,7 +235,7 @@ test('repeated and selected multi-profile dry runs write one ownership manifest
235
235
  test('install.mjs --help documents profile target selection', () => {
236
236
  const result = spawnSync(process.execPath, [INSTALL_MODULE_PATH, '--help'], {
237
237
  encoding: 'utf8',
238
- env: process.env,
238
+ env: { ...process.env, CLAUDE_CONFIG_DIR: undefined, LLM_SETTINGS_PROFILES_ROOT: undefined },
239
239
  });
240
240
  assert.equal(result.status, 0, result.stderr);
241
241
  assert.match(result.stdout, /--profile/);
@@ -247,7 +247,7 @@ test('install.mjs --help documents profile target selection', () => {
247
247
  test('install.mjs --help states that a positional path argument carries no meaning', () => {
248
248
  const result = spawnSync(process.execPath, [INSTALL_MODULE_PATH, '--help'], {
249
249
  encoding: 'utf8',
250
- env: process.env,
250
+ env: { ...process.env, CLAUDE_CONFIG_DIR: undefined, LLM_SETTINGS_PROFILES_ROOT: undefined },
251
251
  });
252
252
  assert.equal(result.status, 0, result.stderr);
253
253
  assert.match(result.stdout, /reads only flags/i);
@@ -259,6 +259,8 @@ function runInstaller(homeDirectory, extraArguments) {
259
259
  function resolveInstallerInvocation(homeDirectory) {
260
260
  const childEnvironment = {
261
261
  ...process.env,
262
+ CLAUDE_CONFIG_DIR: undefined,
263
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
262
264
  CDE_INSTALL_PSTACK: '0',
263
265
  CDE_INSTALL_USAGE_WRAPUP: '0',
264
266
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -22,6 +22,8 @@ function installInScratchHome(homeDirectory, argumentsList = [], extraEnvironmen
22
22
  encoding: 'utf8',
23
23
  env: {
24
24
  ...process.env,
25
+ CLAUDE_CONFIG_DIR: undefined,
26
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
25
27
  CDE_INSTALL_PSTACK: '0',
26
28
  CDE_INSTALL_USAGE_WRAPUP: '0',
27
29
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -25,6 +25,7 @@ import {
25
25
  mergeMissingSettingsDefaults,
26
26
  settingsDefaultsFromPackageSettings,
27
27
  } from './merge_settings_defaults.mjs';
28
+ import { DECLARED_PROFILE_SETTINGS } from './merge_profile_settings.mjs';
28
29
 
29
30
  const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
30
31
  const PACKAGE_SETTINGS_PATH = join(PACKAGE_ROOT, 'settings.json');
@@ -32,6 +33,8 @@ const INSTALL_ENTRY = join(PACKAGE_ROOT, 'bin', 'install.mjs');
32
33
 
33
34
  const EXPECTED_DENY_ENTRIES = [];
34
35
  const EXPECTED_ALLOW_ENTRIES = ['WebFetch(domain:docs.github.com)'];
36
+ const PROFILE_ALLOW_ENTRIES = DECLARED_PROFILE_SETTINGS
37
+ .find((eachEntry) => eachEntry.keyPath.join('.') === 'permissions.allow').items;
35
38
  const SAMPLE_MANAGED_DENY_ENTRIES = ['Edit($HOME/.claude/managed-test/**)'];
36
39
  const SAMPLE_MANAGED_PERMISSIONS = { allow: [], deny: SAMPLE_MANAGED_DENY_ENTRIES };
37
40
  const SAMPLE_MANAGED_ALLOW_PERMISSIONS = { allow: ['WebFetch(domain:example.com)'], deny: [] };
@@ -56,6 +59,8 @@ function runInstallerInSandbox(sandboxHome, installerArguments = []) {
56
59
  encoding: 'utf8',
57
60
  env: {
58
61
  ...process.env,
62
+ CLAUDE_CONFIG_DIR: undefined,
63
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
59
64
  CDE_INSTALL_PSTACK: '0',
60
65
  CDE_INSTALL_USAGE_WRAPUP: '0',
61
66
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -171,7 +176,7 @@ test('sandbox install keeps default managed denies empty on repeat', () => {
171
176
  const firstSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
172
177
  const firstDeny = firstSettings.permissions?.deny ?? [];
173
178
  assert.deepEqual(firstDeny, EXPECTED_DENY_ENTRIES);
174
- assert.deepEqual(firstSettings.permissions?.allow, EXPECTED_ALLOW_ENTRIES);
179
+ assert.deepEqual(firstSettings.permissions?.allow, [...EXPECTED_ALLOW_ENTRIES, ...PROFILE_ALLOW_ENTRIES]);
175
180
 
176
181
  const manifestPath = join(sandboxHome, '.claude', '.claude-dev-env-manifest.json');
177
182
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
@@ -183,7 +188,7 @@ test('sandbox install keeps default managed denies empty on repeat', () => {
183
188
  const secondSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
184
189
  const secondDeny = secondSettings.permissions?.deny ?? [];
185
190
  assert.deepEqual(secondDeny, EXPECTED_DENY_ENTRIES);
186
- assert.deepEqual(secondSettings.permissions?.allow, EXPECTED_ALLOW_ENTRIES);
191
+ assert.deepEqual(secondSettings.permissions?.allow, [...EXPECTED_ALLOW_ENTRIES, ...PROFILE_ALLOW_ENTRIES]);
187
192
  } finally {
188
193
  rmSync(sandboxHome, { recursive: true, force: true });
189
194
  }
@@ -214,7 +219,7 @@ test('a normal upgrade retires manifest-owned denies and preserves user entries'
214
219
 
215
220
  const upgradedSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
216
221
  assert.deepEqual(upgradedSettings.permissions?.deny, [USER_OWNED_DENY_ENTRY]);
217
- assert.deepEqual(upgradedSettings.permissions?.allow, ['Bash(git status)', ...EXPECTED_ALLOW_ENTRIES]);
222
+ assert.deepEqual(upgradedSettings.permissions?.allow, ['Bash(git status)', ...EXPECTED_ALLOW_ENTRIES, ...PROFILE_ALLOW_ENTRIES]);
218
223
  assert.deepEqual(upgradedSettings.permissions?.ask, ['Edit(./**)']);
219
224
 
220
225
  const upgradedManifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
@@ -25,6 +25,7 @@ const PACKAGE_HOOKS_JSON_PATH = fileURLToPath(new URL('../hooks/hooks.json', imp
25
25
  function installEnvironment(homeDirectory) {
26
26
  return {
27
27
  ...process.env,
28
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
28
29
  HOME: homeDirectory,
29
30
  USERPROFILE: homeDirectory,
30
31
  CLAUDE_CONFIG_DIR: '',
@@ -406,7 +406,9 @@ test('mergeHooksIntoSettings expands residual $HOME in preserved user hooks', ()
406
406
  ],
407
407
  },
408
408
  };
409
- mergeHooksIntoSettings(settings, hooksConfig, 'C:/Users/x/.claude', 'C:/Python313/python.exe');
409
+ mergeHooksIntoSettings(
410
+ settings, hooksConfig, 'C:/Users/x/.claude', 'C:/Python313/python.exe', null, 'C:/Users/x',
411
+ );
410
412
  const allCommands = settings.hooks.SessionStart[0].hooks.map(eachHook => eachHook.command);
411
413
  assert.ok(
412
414
  allCommands.includes(
@@ -2980,7 +2982,7 @@ test('the help output states that the installer reads only flags', () => {
2980
2982
  const helpRun = spawnSync(
2981
2983
  process.execPath,
2982
2984
  [fileURLToPath(new URL('./install.mjs', import.meta.url)), '--help'],
2983
- { encoding: 'utf8', env: process.env },
2985
+ { encoding: 'utf8', env: { ...process.env, CLAUDE_CONFIG_DIR: undefined, LLM_SETTINGS_PROFILES_ROOT: undefined } },
2984
2986
  );
2985
2987
 
2986
2988
  assert.equal(helpRun.status, 0, helpRun.stderr);
@@ -3024,6 +3026,7 @@ test('a failed update preserves an existing skills lookup pointer', () => {
3024
3026
  writeFileSync(join(homeDirectory, '.gitconfig'), '');
3025
3027
  const environment = {
3026
3028
  ...process.env,
3029
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
3027
3030
  HOME: homeDirectory,
3028
3031
  USERPROFILE: homeDirectory,
3029
3032
  CLAUDE_CONFIG_DIR: '',
@@ -3075,6 +3078,7 @@ function runPstackInstaller(homeDirectory, extraArguments, environmentOverrides
3075
3078
  encoding: 'utf8',
3076
3079
  env: {
3077
3080
  ...process.env,
3081
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
3078
3082
  HOME: homeDirectory,
3079
3083
  USERPROFILE: homeDirectory,
3080
3084
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -3390,7 +3394,7 @@ test('a failing usage-wrapup command is reported and the install still succeeds'
3390
3394
  test('the help output names the usage-wrapup opt-out beside the pstack one', () => {
3391
3395
  const helpRun = spawnSync(process.execPath, [PSTACK_TEST_INSTALLER_PATH, '--help'], {
3392
3396
  encoding: 'utf8',
3393
- env: process.env,
3397
+ env: { ...process.env, CLAUDE_CONFIG_DIR: undefined, LLM_SETTINGS_PROFILES_ROOT: undefined },
3394
3398
  });
3395
3399
 
3396
3400
  assert.equal(helpRun.status, 0, helpRun.stderr);
@@ -85,6 +85,8 @@ test('capture and restore recover settings, manifest, files, and hooksPath', ()
85
85
  mkdirSync(priorHooksDirectory, { recursive: true });
86
86
  const env = {
87
87
  ...process.env,
88
+ CLAUDE_CONFIG_DIR: undefined,
89
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
88
90
  CDE_INSTALL_PSTACK: '0',
89
91
  CDE_INSTALL_USAGE_WRAPUP: '0',
90
92
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -148,6 +150,8 @@ test('capture and restore recover an additional host settings file', () => {
148
150
  const io = {
149
151
  env: {
150
152
  ...process.env,
153
+ CLAUDE_CONFIG_DIR: undefined,
154
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
151
155
  CDE_INSTALL_PSTACK: '0',
152
156
  CDE_INSTALL_USAGE_WRAPUP: '0',
153
157
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -184,6 +188,8 @@ test('runWithInstallTransaction restores prior state on injected fault', () => {
184
188
  writeFileSync(gitConfigPath, '');
185
189
  const env = {
186
190
  ...process.env,
191
+ CLAUDE_CONFIG_DIR: undefined,
192
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
187
193
  CDE_INSTALL_PSTACK: '0',
188
194
  CDE_INSTALL_USAGE_WRAPUP: '0',
189
195
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -239,6 +245,8 @@ test('runWithInstallTransaction commits and discards journal on success', () =>
239
245
  const io = {
240
246
  env: {
241
247
  ...process.env,
248
+ CLAUDE_CONFIG_DIR: undefined,
249
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
242
250
  CDE_INSTALL_PSTACK: '0',
243
251
  CDE_INSTALL_USAGE_WRAPUP: '0',
244
252
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -283,6 +291,8 @@ test('runWithInstallTransaction commits and discards journal on success', () =>
283
291
  function runInstaller(homeDirectory, extraArguments, options = {}) {
284
292
  const childEnvironment = {
285
293
  ...process.env,
294
+ CLAUDE_CONFIG_DIR: undefined,
295
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
286
296
  CDE_INSTALL_PSTACK: '0',
287
297
  CDE_INSTALL_USAGE_WRAPUP: '0',
288
298
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -463,6 +473,8 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
463
473
  writeGlobalCoreHooksPath(priorHooksPath, {
464
474
  env: {
465
475
  ...process.env,
476
+ CLAUDE_CONFIG_DIR: undefined,
477
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
466
478
  CDE_INSTALL_PSTACK: '0',
467
479
  CDE_INSTALL_USAGE_WRAPUP: '0',
468
480
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -481,6 +493,8 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
481
493
  const restored = readGlobalCoreHooksPath({
482
494
  env: {
483
495
  ...process.env,
496
+ CLAUDE_CONFIG_DIR: undefined,
497
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
484
498
  CDE_INSTALL_PSTACK: '0',
485
499
  CDE_INSTALL_USAGE_WRAPUP: '0',
486
500
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -71,6 +71,8 @@ function writeFileWithParents(filePath, contents) {
71
71
  function runInstaller(homeDirectory, extraArguments, options = {}) {
72
72
  const childEnvironment = {
73
73
  ...process.env,
74
+ CLAUDE_CONFIG_DIR: undefined,
75
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
74
76
  CDE_INSTALL_PSTACK: '0',
75
77
  CDE_INSTALL_USAGE_WRAPUP: '0',
76
78
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -306,6 +308,8 @@ test('uninstall fault after_git_config restores prior core.hooksPath', () => {
306
308
  const io = {
307
309
  env: {
308
310
  ...process.env,
311
+ CLAUDE_CONFIG_DIR: undefined,
312
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
309
313
  CDE_INSTALL_PSTACK: '0',
310
314
  CDE_INSTALL_USAGE_WRAPUP: '0',
311
315
  CDE_INSTALL_SUBAGENT_MODELS: '0',
@@ -22,6 +22,8 @@ const REGISTERED_HOOK_RELATIVE_PATHS = [
22
22
  function installerEnvironment(homeDirectory) {
23
23
  const childEnvironment = {
24
24
  ...process.env,
25
+ CLAUDE_CONFIG_DIR: undefined,
26
+ LLM_SETTINGS_PROFILES_ROOT: undefined,
25
27
  CDE_INSTALL_PSTACK: '0',
26
28
  CDE_INSTALL_USAGE_WRAPUP: '0',
27
29
  CDE_INSTALL_SUBAGENT_MODELS: '0',