fix(install): keep foreign Claude hooks in mixed groups
Remove only setup-owned hook entries from a hook group and keep foreign entries with their group options, such as matcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
857720bcfd
commit
e2394ca1d1
2 files changed
+24
-6
No files matched your search
@@ -73,18 +73,23 @@ def merge_json(current, managed):
|
|||||||
groups = value.get(event, [])
|
groups = value.get(event, [])
|
||||||
old_groups = hooks.get(event, [])
|
old_groups = hooks.get(event, [])
|
||||||
if isinstance(groups, list) and isinstance(old_groups, list):
|
if isinstance(groups, list) and isinstance(old_groups, list):
|
||||||
retained = [group for group in old_groups if not any(
|
retained = []
|
||||||
re.search(r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)',
|
for group in old_groups:
|
||||||
str(hook.get('command', '')) + str(hook.get('command_windows', '')), re.I)
|
if not isinstance(group, dict) or not isinstance(group.get('hooks'), list):
|
||||||
for hook in group.get('hooks', []) if isinstance(hook, dict)
|
retained.append(group)
|
||||||
)]
|
continue
|
||||||
|
foreign = [hook for hook in group['hooks'] if not (isinstance(hook, dict) and re.search(
|
||||||
|
r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)',
|
||||||
|
str(hook.get('command', '')) + str(hook.get('command_windows', '')), re.I))]
|
||||||
|
if foreign:
|
||||||
|
retained.append(dict(group, hooks=foreign))
|
||||||
merged = groups + [group for group in retained if group not in groups]
|
merged = groups + [group for group in retained if group not in groups]
|
||||||
if merged:
|
if merged:
|
||||||
hooks[event] = merged
|
hooks[event] = merged
|
||||||
else:
|
else:
|
||||||
hooks.pop(event, None)
|
hooks.pop(event, None)
|
||||||
else:
|
else:
|
||||||
hooks[event] = value
|
hooks[event] = groups
|
||||||
result[key] = hooks
|
result[key] = hooks
|
||||||
elif isinstance(value, dict) and isinstance(result.get(key), dict):
|
elif isinstance(value, dict) and isinstance(result.get(key), dict):
|
||||||
result[key] = merge_json(result[key], value)
|
result[key] = merge_json(result[key], value)
|
||||||
|
|||||||
@@ -45,6 +45,19 @@ class InstallOptionsTests(unittest.TestCase):
|
|||||||
self.assertEqual(result['mcp_servers'], {'demo': {'command': 'demo', 'env': {'TOKEN_NAME': 'x'}}})
|
self.assertEqual(result['mcp_servers'], {'demo': {'command': 'demo', 'env': {'TOKEN_NAME': 'x'}}})
|
||||||
self.assertEqual(result['model_providers'], {'custom': {'name': 'Custom', 'base_url': 'https://example.invalid'}})
|
self.assertEqual(result['model_providers'], {'custom': {'name': 'Custom', 'base_url': 'https://example.invalid'}})
|
||||||
|
|
||||||
|
def test_merge_json_removes_only_owned_hooks_from_mixed_group(self):
|
||||||
|
owned = {'type': 'command', 'command': 'bash "/old/hooks/flashbang.sh"', 'timeout': 8}
|
||||||
|
foreign = {'type': 'command', 'command': 'notify-done', 'timeout': 3}
|
||||||
|
current = {'hooks': {'Stop': [{'matcher': 'custom', 'hooks': [owned, foreign]}],
|
||||||
|
'PreCompact': [{'hooks': [{'type': 'command', 'command': 'bash "/old/record-compact.sh"'}]}]}}
|
||||||
|
managed_stop = {'hooks': [{'type': 'command', 'command': 'bash "/new/hooks/flashbang.sh"', 'timeout': 8}]}
|
||||||
|
managed = {'hooks': {'Stop': [managed_stop], 'PreCompact': []}}
|
||||||
|
|
||||||
|
hooks = OPTIONS.merge_json(current, managed)['hooks']
|
||||||
|
|
||||||
|
self.assertEqual(hooks['Stop'], [managed_stop, {'matcher': 'custom', 'hooks': [foreign]}])
|
||||||
|
self.assertNotIn('PreCompact', hooks)
|
||||||
|
|
||||||
def test_merge_cli_accepts_bom_input_and_preserves_non_ascii(self):
|
def test_merge_cli_accepts_bom_input_and_preserves_non_ascii(self):
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
current = Path(directory) / 'config.toml'
|
current = Path(directory) / 'config.toml'
|
||||||
|
|||||||
Reference in new issue
Block a user