Skip to content

fix: stale agent hook declarations#2657

Open
janburzinski wants to merge 5 commits into
mainfrom
emdash/hooks-update-npvlz
Open

fix: stale agent hook declarations#2657
janburzinski wants to merge 5 commits into
mainfrom
emdash/hooks-update-npvlz

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • refreshed agent hook declarations
  • added missing lifecycle/session/error hooks
  • fixed stale kilo stop support
  • added mistral tool-use hook
Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refreshes agent hook declarations across multiple providers, adding previously missing lifecycle events (session, start, error), fixing stale declarations (Kilocode's unused stop), addressing the parseClaudeHookEvent notification-type detection bug from a previous review, tightening Copilot's install detection to require all managed keys, and adding Mistral's after_tool hook while correctly excluding ask_user_question.

  • Hook set expansion: Claude, Copilot, Devin, and Droid now register SessionStart, SessionEnd, UserPromptSubmit, and where applicable StopFailure/errorOccurred hooks, aligning supportedEvents arrays across all providers.
  • supportsHooks cleanup: The now-redundant field is removed from AgentProviderDefinition; capability detection in the UI already derives this from agent.capabilities.hooks.kind.
  • Bug fixes carried forward: parseClaudeHookEvent now checks body.notification_type before falling back to message heuristics; Copilot's getHooksInstalled switches from .some() to .every() over all seven managed keys.

Confidence Score: 5/5

Safe to merge — all three issues flagged in previous review rounds are addressed, the changes are consistent across providers, and the new test coverage validates the fixed behaviors.

The hook additions follow the exact same pattern already established for Claude and Devin, the supportsHooks removal is safe because the UI derives that flag from capabilities rather than the registry field, and Kilocode's stop removal is confirmed correct by reading the plugin file (it never emitted stop). The two new test files directly cover the previously reported notification_type and partial-install bugs.

No files require special attention.

Important Files Changed

Filename Overview
packages/plugins/src/agents/impl/claude/hooks.ts Adds SessionStart, PermissionRequest, StopFailure, and SessionEnd hooks; fixes notification_type detection by checking body fields before message regex — addresses previous P1
packages/plugins/src/agents/impl/copilot/hooks.ts Expands from 3 to 7 managed hook keys, switches install detection to .every(), and actively registers notification hook instead of stripping it — addresses previous thread
packages/plugins/src/agents/impl/mistral/hooks.ts Adds emdash-after-tool hook with negative-lookahead regex to exclude ask_user_question, preventing duplicate tool-use events — addresses previous thread
packages/plugins/src/agents/impl/kilocode/index.ts Removes stale stop from supportedEvents (kilocode plugin never emits stop, only session/error/notification); replaces with error which the plugin does emit
packages/core/src/agents/plugins/helpers/parse-hook-event.ts Adds tool-use → start and tool-use-failure → error mappings to defaultHookEventParser, with new tests covering both cases
apps/emdash-desktop/src/shared/core/agents/agent-provider-registry.ts Removes dead supportsHooks field from AgentProviderDefinition and all agent entries; UI derives this from agent.capabilities.hooks.kind instead
packages/plugins/src/agents/impl/devin/hooks.ts Adds SessionStart (session) and UserPromptSubmit (start) hooks to Devin, consistent with other agent providers
packages/plugins/src/agents/impl/droid/hooks.ts Adds SubagentStop and SessionEnd hooks for Droid, both mapping to stop event
packages/core/src/agents/plugins/capabilities/hooks-types.ts Adds error to HOOK_EVENTS constant; previously error was handled in defaultHookEventParser but not declared in the canonical set

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Agent as Agent (Claude/Copilot/Devin/Droid)
    participant Hook as Hook Command
    participant Emdash as Emdash Hook Server
    participant Parser as parseHookEvent

    Agent->>Hook: SessionStart fires
    Hook->>Emdash: POST /hook (event-type: session)
    Emdash->>Parser: defaultHookEventParser('session', body)
    Parser-->>Emdash: "{ kind: 'session', providerSessionId }"

    Agent->>Hook: UserPromptSubmit fires
    Hook->>Emdash: POST /hook (event-type: start)
    Emdash->>Parser: defaultHookEventParser('start', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'start' }"

    Agent->>Hook: PermissionRequest fires
    Hook->>Emdash: "POST /hook (event-type: notification, body: {notification_type: 'permission_prompt'})"
    Emdash->>Parser: parseClaudeHookEvent('notification', body)
    Note over Parser: Checks body.notification_type first
    Parser-->>Emdash: "{ kind: 'status', type: 'notification', notificationType: 'permission_prompt' }"

    Agent->>Hook: Stop fires
    Hook->>Emdash: POST /hook (event-type: stop)
    Emdash->>Parser: defaultHookEventParser('stop', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'stop' }"

    Agent->>Hook: StopFailure / errorOccurred fires
    Hook->>Emdash: POST /hook (event-type: error)
    Emdash->>Parser: defaultHookEventParser('error', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'error' }"

    Agent->>Hook: SessionEnd fires
    Hook->>Emdash: POST /hook (event-type: stop)
    Emdash->>Parser: defaultHookEventParser('stop', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'stop' }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Agent as Agent (Claude/Copilot/Devin/Droid)
    participant Hook as Hook Command
    participant Emdash as Emdash Hook Server
    participant Parser as parseHookEvent

    Agent->>Hook: SessionStart fires
    Hook->>Emdash: POST /hook (event-type: session)
    Emdash->>Parser: defaultHookEventParser('session', body)
    Parser-->>Emdash: "{ kind: 'session', providerSessionId }"

    Agent->>Hook: UserPromptSubmit fires
    Hook->>Emdash: POST /hook (event-type: start)
    Emdash->>Parser: defaultHookEventParser('start', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'start' }"

    Agent->>Hook: PermissionRequest fires
    Hook->>Emdash: "POST /hook (event-type: notification, body: {notification_type: 'permission_prompt'})"
    Emdash->>Parser: parseClaudeHookEvent('notification', body)
    Note over Parser: Checks body.notification_type first
    Parser-->>Emdash: "{ kind: 'status', type: 'notification', notificationType: 'permission_prompt' }"

    Agent->>Hook: Stop fires
    Hook->>Emdash: POST /hook (event-type: stop)
    Emdash->>Parser: defaultHookEventParser('stop', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'stop' }"

    Agent->>Hook: StopFailure / errorOccurred fires
    Hook->>Emdash: POST /hook (event-type: error)
    Emdash->>Parser: defaultHookEventParser('error', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'error' }"

    Agent->>Hook: SessionEnd fires
    Hook->>Emdash: POST /hook (event-type: stop)
    Emdash->>Parser: defaultHookEventParser('stop', body)
    Parser-->>Emdash: "{ kind: 'status', type: 'stop' }"
Loading

Reviews (2): Last reviewed commit: "fix(hooks): handle tool-use events" | Re-trigger Greptile

Comment thread packages/plugins/src/agents/impl/copilot/hooks.ts Outdated
Comment thread packages/plugins/src/agents/impl/mistral/hooks.ts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to get rid of this after the plugin refactor now, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid!! was just a little unsure. removed it :D

@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants