[FEATURE]: SessionStart hook for session lifecycle events
Feature hasn't been suggested before.
- [x] I have verified this feature I'm about to request hasn't been suggested before.
Request
Add a SessionStart hook (similar to Claude Code) that runs at key session lifecycle points.
Demo
POC: https://github.com/simonwjackson/opencode/pull/new/feature/session-start-hook

Use Cases
- Load development context (existing issues, recent codebase changes)
- Install dependencies or run setup scripts
- Initialize project-specific tooling
- Run validation checks before a session begins
Motivation
I have a custom tool that gathers project context (git status, package.json, tsconfig, directory tree) to augment AGENTS.md.
The problem: Manually calling it adds ~12 seconds before I can start working.
What I want: This should run automatically in the background before the first message is sent. Subagents would load this automatically as well.
Why This Can't Be Done With Plugins Today
I investigated the current plugin system and found:
What works:
- Plugin init function runs once at OpenCode bootstrap (approximates
startup) eventhook can react tosession.createdandsession.compacted
What's missing:
resume- No event fires when resuming via--continue/--session(it just navigates to an existing session)- No way to distinguish why a session started
The core limitation is that resume is a UI navigation operation that doesn't fire a bus event.
Proposed Solution
A new plugin hook that fires at session boundaries:
"session.start"?: (input: {
/** What triggered this hook */
trigger: "startup" | "resume" | "compact"
/** Session ID (when resuming an existing session) */
sessionID?: string
}) => Promise<void>
Triggers
| Trigger | When it fires |
|---|---|
startup |
Fresh OpenCode start |
resume |
--resume, --continue, or /resume |
compact |
Auto or manual compaction |
Example
export const TestSessionStartPlugin = async ({ $ }) => ({
"session.start": async (input, output) => {
const gitBranch = await $`git branch --show-current 2>/dev/null`.text().catch(() => "unknown")
const gitStatus = await $`git status --short 2>/dev/null`.text().catch(() => "")
const recentCommits = await $`git log --oneline -3 2>/dev/null`.text().catch(() => "")
output.additionalContext = `<project-context>
## Current Branch: ${gitBranch.trim()}
## Uncommitted Changes
${gitStatus.trim() || '(none)'}
## Recent Commits
${recentCommits.trim() || '(none)'}
</project-context>`
}
})
References
- Claude Code SessionStart Hook - Prior art for this feature
This feature request appears to be unique and doesn't duplicate existing issues. However, it's related to several ongoing plugin system improvements:
- #5148 - Comprehensive Plugin Pipeline (middleware-style data flow control)
- #4726 - Custom tools
installhook (initialization hooks for tools) - #5305 - Plugin Hook for Instant TUI Commands (expanding plugin hook system)
These issues are complementary to your SessionStart hook proposal and might benefit from coordinated implementation.
Tried the POC branch - the session.start hook works for startup (new sessions) but fails on resume when using --continue. Getting "No context found for instance" error because Plugin.triggerSessionStart is called during route navigation before the plugin context is fully initialized. The hook fires (confirmed via logging) but the plugin system isn't ready yet at that point in the --continue bootstrap sequence.