claude-dev-env 8.26.5 → 8.26.6

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.
@@ -239,6 +239,66 @@ test('reinstall moves the old routing hook and keeps user hooks', () => {
239
239
  }
240
240
  });
241
241
 
242
+ test('Codex reinstall removes omitted package hooks and preserves user configuration', () => {
243
+ const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-codex-migration-'));
244
+ try {
245
+ const codexHome = join(homeDirectory, 'custom-codex');
246
+ const codexHooksPath = join(codexHome, 'hooks.json');
247
+ const configPath = join(codexHome, 'config.toml');
248
+ const disabledConfig = '[features]\nhooks = false\n';
249
+ const allLegacyRegistrations = [
250
+ ['PreToolUse', 'Edit', '.claude', 'blocking/pre_tool_use_dispatcher.py'],
251
+ ['PreToolUse', 'Bash', '.agents', 'blocking/bash_pre_tool_use_dispatcher.py'],
252
+ ['SessionStart', '*', 'custom-codex', 'session/issue_tracker_session_starter.py'],
253
+ ['Stop', '*', '.claude', 'session/skill_loaded_reminder.py'],
254
+ ['SessionEnd', '*', 'custom-codex', 'lifecycle/session_end_cleanup.py'],
255
+ ];
256
+ const hooksByEvent = {};
257
+ for (const [event, matcher, root, script] of allLegacyRegistrations) {
258
+ hooksByEvent[event] ??= [];
259
+ hooksByEvent[event].push({
260
+ matcher,
261
+ metadata: 'keep-group',
262
+ hooks: [
263
+ { type: 'command', command: `python "${join(homeDirectory, root, 'hooks', script)}"` },
264
+ { type: 'command', command: `user-${event}-${matcher}`, timeout: 7 },
265
+ ],
266
+ });
267
+ }
268
+ const foreignCommand = `python "${join(homeDirectory, 'foreign', '.claude', 'hooks', 'blocking', 'pre_tool_use_dispatcher.py')}"`;
269
+ hooksByEvent.PreToolUse.push({ matcher: 'foreign', hooks: [{ type: 'command', command: foreignCommand }] });
270
+ const suffixCommand = `python "${join(codexHome, 'hooks', 'blocking', 'pre_tool_use_dispatcher.py.backup')}"`;
271
+ hooksByEvent.PreToolUse.push({ matcher: 'suffix', hooks: [{ type: 'command', command: suffixCommand }] });
272
+ hooksByEvent.CustomEvent = { metadata: 'keep-event' };
273
+ const expectedUserHooks = JSON.parse(JSON.stringify(hooksByEvent));
274
+ for (const eachGroup of Object.values(expectedUserHooks).flat()) {
275
+ if (eachGroup.hooks && eachGroup.metadata) eachGroup.hooks.shift();
276
+ }
277
+ hooksByEvent.LegacyValidator = [{ hooks: [{ type: 'command', command: 'python -c "sys.path.insert(0, __claude_dev_env_managed_hooks__); from validators.run_all_validators import main; main()"' }] }];
278
+ mkdirSync(codexHome, { recursive: true });
279
+ writeFileSync(configPath, disabledConfig);
280
+ writeFileSync(codexHooksPath, JSON.stringify({ enabled: false, metadata: 'keep-file', hooks: hooksByEvent }));
281
+
282
+ runInstaller(homeDirectory, ['--only', 'core'], { CODEX_HOME: codexHome });
283
+
284
+ const migratedHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
285
+ const allSpawnGroups = migratedHooks.hooks.PreToolUse.filter(eachGroup =>
286
+ ['Agent|Task', 'multi_agent_v1__spawn_agent'].includes(eachGroup.matcher));
287
+ assert.equal(allSpawnGroups.length, 2);
288
+ migratedHooks.hooks.PreToolUse = migratedHooks.hooks.PreToolUse.filter(eachGroup => !allSpawnGroups.includes(eachGroup));
289
+ assert.deepEqual(migratedHooks, { enabled: false, metadata: 'keep-file', hooks: expectedUserHooks });
290
+ assert.equal(readFileSync(configPath, 'utf8'), disabledConfig);
291
+ const firstInstall = readFileSync(codexHooksPath, 'utf8');
292
+ runInstaller(homeDirectory, ['--only', 'core'], { CODEX_HOME: codexHome });
293
+ assert.equal(readFileSync(codexHooksPath, 'utf8'), firstInstall);
294
+ const claudeSettings = JSON.parse(readFileSync(join(homeDirectory, '.claude', 'settings.json'), 'utf8'));
295
+ assert.ok(claudeSettings.hooks.SessionStart.length > 0);
296
+ assert.ok(claudeSettings.hooks.PreToolUse.some(eachGroup => eachGroup.matcher === 'Bash'));
297
+ } finally {
298
+ rmSync(homeDirectory, { recursive: true, force: true });
299
+ }
300
+ });
301
+
242
302
  test('uninstall removes the routing hook and keeps a Codex user hook', () => {
243
303
  const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-uninstall-'));
244
304
  try {
package/bin/install.mjs CHANGED
@@ -2038,6 +2038,7 @@ function mergeHooksAtPath(
2038
2038
  pythonCommand,
2039
2039
  selectHooksConfig = hooksConfig => hooksConfig,
2040
2040
  pluginRootDir = CLAUDE_HOME,
2041
+ shouldPruneSourceHooks = false,
2041
2042
  ) {
2042
2043
  const hooksSource = resolvePackageManagedDirectory(
2043
2044
  hooksSourceRoot,
@@ -2045,8 +2046,17 @@ function mergeHooksAtPath(
2045
2046
  );
2046
2047
  const hooksJsonPath = join(hooksSource, 'hooks.json');
2047
2048
  if (!existsSync(hooksJsonPath)) return 0;
2048
- const hooksConfig = selectHooksConfig(JSON.parse(readFileSync(hooksJsonPath, 'utf8')));
2049
+ const sourceHooksConfig = JSON.parse(readFileSync(hooksJsonPath, 'utf8'));
2049
2050
  const settings = loadClaudeSettingsObject(settingsPath);
2051
+ if (shouldPruneSourceHooks && settings.hooks && typeof settings.hooks === 'object') {
2052
+ stripRetiredHookEntries(
2053
+ settings,
2054
+ managedHookScriptRelativePaths(sourceHooksConfig),
2055
+ retiredHookOwnership(),
2056
+ );
2057
+ pruneManagedHooksFromSettings(settings, new Set());
2058
+ }
2059
+ const hooksConfig = selectHooksConfig(sourceHooksConfig);
2050
2060
  const groupCount = mergeHooksIntoSettings(settings, hooksConfig, pluginRootDir, pythonCommand);
2051
2061
  writeFileSync(settingsPath, JSON.stringify(settings, null, indentation) + '\n');
2052
2062
  return groupCount;
@@ -2081,6 +2091,7 @@ function mergeCodexHooks(hooksSourceRoot, pythonCommand) {
2081
2091
  pythonCommand,
2082
2092
  selectCodexNativeHooksConfig,
2083
2093
  dirname(CODEX_HOOKS_CONFIGURATION_PATH),
2094
+ true,
2084
2095
  );
2085
2096
  }
2086
2097
 
@@ -16,7 +16,7 @@ import { fileURLToPath } from 'node:url';
16
16
  import { detectPython } from './install.mjs';
17
17
  const THIS_DIRECTORY = dirname(fileURLToPath(import.meta.url));
18
18
  const INSTALLER_PATH = join(THIS_DIRECTORY, 'install.mjs');
19
- const INSTALLER_PROCESS_TIMEOUT_MS = 60_000;
19
+ const INSTALLER_PROCESS_TIMEOUT_MS = 120_000;
20
20
  const EXCLUDED_PACKAGE_COPY_DIRECTORY = 'node_modules';
21
21
 
22
22
  const RETIRED_SKILL_DIRECTORIES = [
@@ -373,7 +373,13 @@ test('installer fault after_settings_write restores prior settings and files', (
373
373
  const { claudeDirectory, rulesFile } = seedPriorInstall(homeDirectory);
374
374
  const priorSettings = readFileSync(join(claudeDirectory, 'settings.json'), 'utf8');
375
375
  const codexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
376
- writeFileWithParents(codexHooksPath, '{"hooks":{"PreToolUse":[{"matcher":"custom"}]}}\n');
376
+ writeFileWithParents(codexHooksPath, JSON.stringify({
377
+ enabled: false,
378
+ hooks: {
379
+ PreToolUse: [{ matcher: 'custom' }],
380
+ SessionEnd: [{ hooks: [{ type: 'command', command: `python "${join(homeDirectory, '.codex', 'hooks', 'lifecycle', 'session_end_cleanup.py')}"` }] }],
381
+ },
382
+ }) + '\n');
377
383
  const priorCodexHooks = readFileSync(codexHooksPath, 'utf8');
378
384
 
379
385
  const failedRun = runInstaller(homeDirectory, [], {
@@ -26,8 +26,17 @@ Run `codex-compat bridge --surface <name> --payload '<json-object>'`. The bridge
26
26
 
27
27
  The package installer seeds `subagent-model-policy.json` in the agents-home
28
28
  `rules/` directory when the file is absent. It registers one `PreToolUse` group
29
- for `multi_agent_v1__spawn_agent` in `$CODEX_HOME/hooks.json`. Existing Codex
30
- hook groups stay in place. Claude-only hook groups do not enter the Codex file.
29
+ for `multi_agent_v1__spawn_agent` and one for `Agent|Task` in `$CODEX_HOME/hooks.json`.
30
+ Before adding these groups, the installer removes obsolete package-owned hook
31
+ registrations from every event. Custom hook entries and Codex's enabled or
32
+ disabled hook settings stay unchanged. Claude-only hook groups do not enter the
33
+ Codex file.
34
+
35
+ A detached Windows Codex daemon can open a console for each background command.
36
+ If this happens, run local interactive sessions with `codex --no-daemon` and keep
37
+ `codex exec` commands unchanged. [Codex issue #44768](https://github.com/openai/codex/issues/44768)
38
+ describes the symptom. [PR #49164](https://github.com/openai/codex/pull/49164)
39
+ adds the upstream console fix.
31
40
 
32
41
  Edit the installed policy file to change the next routing decision. The resolver
33
42
  loads that path for each invocation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.26.5",
3
+ "version": "8.26.6",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {