-
-
Notifications
You must be signed in to change notification settings - Fork 301
feat(hooks): add SessionStart hook support for Claude Code plugins #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
- Add SessionStart to ClaudeHookEvent union and ClaudeHooksConfig
- Add SessionStartInput/SessionStartOutput interfaces in types.ts
- Create session-start.ts executor that wraps output in <session-start-hook> tags
- Wire SessionStart execution to session.created event in index.ts
- Add loadPluginHooksConfigs() to load hooks from ~/.claude/plugins/
- Fix replace() -> replaceAll() for ${CLAUDE_PLUGIN_ROOT} variable resolution
This enables Claude Code plugins like claude-mem to inject context at
session start, matching the behavior in Claude Code.
|
All contributors have signed the CLA. Thank you! ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
Greptile SummaryAdds Key Changes:
Implementation follows existing patterns:
The implementation is clean, well-structured, and integrates seamlessly with the existing hook infrastructure. Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant OC as OpenCode
participant Hook as ClaudeCodeHooks
participant Config as config.ts
participant Exec as session-start.ts
participant Plugin as Plugin Hook Command
participant Injector as injectHookMessage()
OC->>Hook: event: session.created
Note over Hook: Extract sessionID & directory
Hook->>Hook: Check if SessionStart disabled
Hook->>Config: loadClaudeHooksConfig()
Config->>Config: Load from settings.json files
Config->>Config: loadPluginHooksConfigs()
Config->>Config: Read installed_plugins.json
Config->>Config: Check enabledPlugins
Config->>Config: Read hooks/hooks.json from each plugin
Config->>Config: Resolve ${CLAUDE_PLUGIN_ROOT} paths
Config->>Config: Merge all hooks configs
Config-->>Hook: ClaudeHooksConfig
Hook->>Exec: executeSessionStartHooks(ctx, config)
Exec->>Exec: Find matching SessionStart hooks
loop For each hook command
Exec->>Plugin: Execute hook command with stdin
Note over Plugin: Receives SessionStartInput JSON:<br/>{session_id, cwd, transcript_path}
Plugin->>Plugin: Process context (e.g. load memory)
Plugin-->>Exec: stdout (JSON or raw text)
Exec->>Exec: Parse output & wrap in tags
Note over Exec: <session-start-hook><br/>context<br/></session-start-hook>
end
Exec-->>Hook: SessionStartResult{context[], elapsedMs, hookName}
alt context.length > 0
Hook->>Injector: injectHookMessage(sessionID, hookContent, {path})
Injector->>Injector: Create message & part files
Injector-->>Hook: success
Hook->>Hook: Log injection status
end
Hook-->>OC: Complete
|
Greptile found no issues!From now on, if a review finishes and we haven't found any issues, we will not post anything, but you can confirm that we reviewed your changes in the status check section. This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR". |
|
Ok, in fact, the idle/session end might not be working fully yet. Let me investigate a bit more |
…mprovements - Add SessionEnd hook support for session cleanup - Add session state file tracking (~/.claude-mem/opencode-sessions.json) - Implement 60s delayed Stop hook matching Claude Code's idle_prompt behavior - Fix transcript format to match Claude Code (message.content wrapper) - Add message.part.updated handler to record assistant text responses - Clean up session tracking on session.deleted
|
@sisyphus-dev-ai please review this, because as far as i know, session start and session end cannot be implemented well cuz opencode have not sufficient hook event for that. investigate and comment on here- including some hacky way too ulw |
|
👀 Looking into this now. Will investigate OpenCode's hook event system and comment back with findings on session start/end implementation feasibility. |
Investigation Complete: SessionStart/SessionEnd Hook FeasibilityI've analyzed the PR implementation against both OpenCode's event system and Claude Code's hook specifications. Here's the complete breakdown: OpenCode Events Available (from source analysis)
Claude Code Hook Events vs PR Implementation
Detailed Analysis1. SessionStart →
|
| Workaround | Purpose | Effectiveness |
|---|---|---|
| 60s delay timer | Match Claude Code's idle_prompt behavior | |
| Activity cancellation | Prevent false Stop triggers | ✅ Good |
| Session state file | Track session lifecycle externally | ✅ Clever |
| Orphan detection | Handle abandoned sessions | ✅ Good for cleanup |
Recommendations
-
Migrate from
session.idletosession.status- The deprecated event may be removed in future OpenCode versions:if (event.type === "session.status") { const status = event.properties.status as { type: string } if (status.type === "idle") { // Handle idle } }
-
Document the semantic differences - Users expecting exact Claude Code behavior should know:
- SessionEnd only fires on explicit session deletion
- Stop hooks have a 60s delay before firing
- SessionStart doesn't fire on resume/compact/clear
-
Consider alternative for SessionEnd - Could potentially hook into:
server.instance.disposedfor cleanup when OpenCode shuts down- Or accept this limitation and rely on orphan session cleanup
-
The 60s delay - This is actually reasonable. It matches Claude Code's
idle_promptnotification timing. Users who need immediate Stop hook execution can use the existingsession.idletrigger without the delay by bypassing the scheduler.
Verdict
Can SessionStart/SessionEnd be implemented "well"?
Partially yes, partially no:
| Feature | Can implement well? | Notes |
|---|---|---|
| SessionStart (new sessions) | ✅ Yes | Maps cleanly to session.created |
| SessionStart (resume/compact) | ❌ No | OpenCode doesn't emit events for these |
| SessionEnd (explicit delete) | ✅ Yes | Maps to session.deleted |
| SessionEnd (implicit close) | ❌ No | No event for TUI close/exit |
| Stop (with delay) | Works with 60s delay heuristic |
The PR's approach is the best possible given OpenCode's current event system. The session state tracking is a smart workaround for the missing lifecycle events.
Code Quality Notes
The implementation looks solid:
- ✅ Clean separation of concerns (session-start.ts, session-end.ts, session-state.ts)
- ✅ Activity tracking to prevent false triggers
- ✅ Orphan session detection for cleanup
⚠️ Should usesession.statusinstead of deprecatedsession.idle⚠️ Thereplace()→replaceAll()fix is good
Overall: This PR represents the best-effort implementation given OpenCode's limitations. The hacky workarounds are well-designed and handle edge cases reasonably.
|
@code-yeongyu yeah, right intuition, some of the things beyond control of this plugin unfortunately.. I'm still working on this but decided to make it in this way:
|
Summary
Adds support for
SessionStarthooks from Claude Code plugins, enabling plugins like claude-mem to inject context at session start - matching the behavior in Claude Code.Changes
types.ts: AddSessionStarttoClaudeHookEventunion andClaudeHooksConfiginterface, plusSessionStartInput/SessionStartOutputinterfacessession-start.ts(new): Executor for SessionStart hooks that wraps output in<session-start-hook>tagsconfig.ts: AddloadPluginHooksConfigs()to load hooks from~/.claude/plugins/*/hooks/hooks.jsonand merge with settings.json hooksconfig-loader.ts: AddSessionStarttoDisabledHooksConfiginterfaceindex.ts: Wire SessionStart execution tosession.createdevent viainjectHookMessage()loader.ts: Fixreplace()→replaceAll()for${CLAUDE_PLUGIN_ROOT}variable resolution (bug: only first occurrence was replaced)How It Works
session.createdevent,executeSessionStartHooks()is called~/.claude/plugins/installed_plugins.jsonand merged with settings.json hookscwdset to the project directory<session-start-hook>tags and injected into the sessionTesting
Tested with claude-mem's native hooks - memory context is successfully injected at session start.
Related