mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(claude): keep a user-set async flag on Orca-managed hooks (#26736)
applyManagedHooks rebuilt each managed Claude hook from scratch, so an explicit "async" a user added to an Orca hook in settings.json was wiped on every install. Carry the boolean over from the managed hook being replaced for the same event; fresh installs stay synchronous and user-owned hooks never donate their value. Fixes #26718 Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
co-authored by
Neil
parent
d58ab9762c
commit
1df8501246
@@ -216,6 +216,18 @@ function hookHasManagedCommand(hook: HookCommandConfig, matches: (value?: string
|
||||
return matches(hook.command) || args.some((arg) => typeof arg === 'string' && matches(arg))
|
||||
}
|
||||
|
||||
// Users may opt a managed hook into async; keep that choice when the hook is rewritten.
|
||||
export function withPreviousManagedAsync(
|
||||
hook: HookCommandConfig,
|
||||
definitions: HookDefinition[],
|
||||
matches: (command: string | undefined) => boolean
|
||||
): HookCommandConfig {
|
||||
const async = definitions
|
||||
.flatMap((definition) => (Array.isArray(definition.hooks) ? definition.hooks : []))
|
||||
.find((prev) => typeof prev.async === 'boolean' && hookHasManagedCommand(prev, matches))?.async
|
||||
return async === undefined ? hook : { ...hook, async }
|
||||
}
|
||||
|
||||
export function hookDefinitionHasManagedCommand(
|
||||
definition: HookDefinition,
|
||||
isManagedCommand: (command: string | undefined) => boolean
|
||||
|
||||
@@ -53,6 +53,37 @@ function expectUserSettingsKept(config: HooksConfig): void {
|
||||
}
|
||||
|
||||
describe('Claude managed hook events by resolved version', () => {
|
||||
it.each([true, false])(
|
||||
'keeps a managed hook async=%s across reinstall and migration',
|
||||
(async) => {
|
||||
const userAsyncHook = { type: 'command' as const, command: 'echo user', async: !async }
|
||||
const oldManagedHook = {
|
||||
type: 'command' as const,
|
||||
command: '/bin/sh',
|
||||
args: ['/old/.orca/agent-hooks/claude-hook.sh'],
|
||||
async
|
||||
}
|
||||
const written = install(
|
||||
{
|
||||
hooks: {
|
||||
PreToolUse: [{ hooks: [userAsyncHook, oldManagedHook] }],
|
||||
PostToolUse: [{ hooks: [userAsyncHook] }]
|
||||
}
|
||||
},
|
||||
'2.1.32'
|
||||
)
|
||||
expect(written.hooks?.PreToolUse).toEqual([
|
||||
{ hooks: [userAsyncHook] },
|
||||
{ matcher: '*', hooks: [{ ...managedHook, async }] }
|
||||
])
|
||||
expect(written.hooks?.PostToolUse).toEqual([
|
||||
{ hooks: [userAsyncHook] },
|
||||
{ matcher: '*', hooks: [managedHook] }
|
||||
])
|
||||
expect(install(written, '2.1.32')).toEqual(written)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(Object.keys(enums))('writes only events Claude %s knows', (version) => {
|
||||
const written = install({ hooks: {} }, version)
|
||||
const unknown = Object.keys(written.hooks ?? {}).filter(
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
isSafeUnicodeWindowsBatchHookPath,
|
||||
MANAGED_HOOK_TIMEOUT_SECONDS,
|
||||
removeManagedCommands,
|
||||
withPreviousManagedAsync,
|
||||
wrapWindowsPowerShellEncodedCommand,
|
||||
type HookCommandConfig,
|
||||
type HookDefinition,
|
||||
type HooksConfig
|
||||
} from '../agent-hooks/installer-utils'
|
||||
import { quotePowerShellLiteral } from '../../shared/powershell-native-argument'
|
||||
@@ -238,12 +238,10 @@ export function applyManagedHooks(
|
||||
|
||||
for (const event of plan.install) {
|
||||
const current = nextHooks[event.eventName]
|
||||
const cleaned = Array.isArray(current) ? removeManagedCommands(current, isManagedCommand) : []
|
||||
const definition: HookDefinition = {
|
||||
...event.definition,
|
||||
hooks: [hookForEvent(event.eventName)]
|
||||
}
|
||||
nextHooks[event.eventName] = [...cleaned, definition]
|
||||
const previous = Array.isArray(current) ? current : []
|
||||
const hook = withPreviousManagedAsync(hookForEvent(event.eventName), previous, isManagedCommand)
|
||||
const cleaned = removeManagedCommands(previous, isManagedCommand)
|
||||
nextHooks[event.eventName] = [...cleaned, { ...event.definition, hooks: [hook] }]
|
||||
}
|
||||
|
||||
for (const event of plan.retire) {
|
||||
|
||||
Reference in New Issue
Block a user