SessionStart hooks fire on every user prompt instead of only at session
start. The root cause is clearSessionHookState(), called on every
session.idle event, which clears sessionFirstMessageProcessed. This
resets the isFirstMessage guard, making it always return true, so
SessionStart hooks execute on every prompt.
sessionFirstMessageProcessed is session-level state (tracks whether the
first message has been processed) and should only be cleared in
clearAllSessionHookState() on session deletion/disposal, not on idle.
sessionErrorState and sessionInterruptState remain cleared on idle since
they are per-response transient state.
Eliminates 19 unguarded `Bun.*` runtime call sites in the plugin bundle
that crashed with `ReferenceError: Bun is not defined` under Electron.
Per-tool-call hot paths (executed on every Read/Edit):
- src/tools/hashline-edit/hash-computation.ts: Bun.hash.xxHash32 → bunHashXxh32
- src/tools/hashline-edit/hashline-edit-executor.ts: 8 sites via bunFile/bunWrite
- src/hooks/hashline-read-enhancer/hook.ts: Bun.file → bunFile
- src/hooks/hashline-edit-diff-enhancer/hook.ts: 2 sites via bunFile
Plugin-load paths:
- src/hooks/claude-code-hooks/config.ts and config-loader.ts: Bun.file → bunFile
- src/features/claude-code-mcp-loader/loader.ts: Bun.file → bunFile
- src/features/claude-code-plugin-loader/mcp-server-loader.ts: Bun.file → bunFile
- src/features/team-mode/deps.ts: Bun.spawn → spawn shim
- src/hooks/session-notification-utils.ts: Bun.which → bunWhich, also drops
the bare `declare const Bun` ambient declaration
- src/shared/binary-downloader.ts: Bun.write → bunWrite
Pure mechanical API swaps. No control-flow or signature changes.
Handle OpenCode session events that carry the session ID under properties.info.id or properties.info.sessionID so background tasks and continuation hooks do not miss idle/error/delete events.
Add regression coverage for nested session.idle events completing background tasks and waking continuation hooks.
When a hook returns exit code 2 (deny) or 1 (ask), previously accumulated
modifiedInput and common fields from earlier allow hooks were discarded.
Now all exit paths (exit code and JSON) include accumulated state.
When a hook returns 'allow' with updatedInput or common fields
(suppressOutput, systemMessage, etc.), these values were silently
dropped. Now they are accumulated across hooks and included in the
final result, matching Claude Code's behavior where allow hooks can
still modify tool input and set metadata.
Revert the create-hooks.ts change — claudeCodeHooks is already created
in createTransformHooks() with proper config and contextCollector.
The previous commit overwrote it with a degraded instance (empty config,
no contextCollector).
Add 8 unit tests for executePreToolUseHooks covering:
- null/empty config handling
- exit code 2 (deny) and 1 (ask) behavior
- multiple merged hooks: allow continues to next hook (the actual bug)
- deny short-circuits remaining hooks
- modifiedInput propagation between hooks
TDD verified: tests fail with original code, pass with fix.
When multiple hook sources are merged (global ~/.claude/settings.json +
project .claude/settings.json), a global catch-all hook returning 'allow'
caused early return before project-level hooks could execute.
Only 'deny' and 'ask' decisions should short-circuit. 'allow' should
continue processing remaining hooks so project-specific guards (e.g.,
file budget enforcement) get a chance to block.
- Add afterAll(() => { mock.restore() }) to 52 test files missing cleanup
- Rewrite create-tool-guard-hooks.test.ts to use spyOn instead of barrel mock
- Fix skill-mcp-manager OAuth tests with missing mockTokens/mockLogin definitions
- Fix start-work hook: show worktree active block on resume with existing worktree_path
- Extract createWorktreeActiveBlock to worktree-block.ts to avoid circular import
- Replace 80-line isolated test runner CI config with single `bun test` command
Add TLS requirement for HTTP hook destinations:
- Warn when plain http:// URLs are used
- Reject remote http:// in production mode
- Allow http://localhost and http://127.0.0.1 for dev
Prevents secret exfiltration over unencrypted channels.
Reduce repeated session.idle work by reusing hook config loads across a short TTL and by retrying parent session lookup instead of permanently caching transient failures.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Move loadClaudeHooksConfig and loadPluginExtendedConfig after isHookDisabled check
in both tool-execute-before and tool-execute-after handlers to skip 5 file reads
per tool call when hooks are disabled (C1)
- Replace two-pass env interpolation with single-pass combined regex to
prevent re-interpolation of $-sequences in substituted header values
- Convert HookEntry to discriminated union so type: "http" requires url,
preventing invalid configs from passing type checking
- Add regression test for double-interpolation edge case
Add type: "http" hook support matching Claude Code's HTTP hook specification.
HTTP hooks send POST requests with JSON body, support env var interpolation
in headers via allowedEnvVars, and configurable timeout.
New files:
- execute-http-hook.ts: HTTP hook execution with env var interpolation
- dispatch-hook.ts: Unified dispatcher for command and HTTP hooks
- execute-http-hook.test.ts: 14 tests covering all HTTP hook scenarios
Modified files:
- types.ts: Added HookHttp interface, HookAction union type
- config.ts: Updated to accept HookAction in raw hook matchers
- pre-tool-use/post-tool-use/stop/user-prompt-submit/pre-compact:
Updated all 5 executors to dispatch HTTP hooks via dispatchHook()
- plugin-loader/types.ts: Added "http" to HookEntry type union
Apply the internal initiator marker to automated continuation, recovery, babysitter, stop-hook, and hook-message injections so Copilot attribution consistently sets x-initiator=agent for system-generated prompts.