Repository navigation
[FEATURE]: SessionStart hook for session lifecycle events #5409
Copy link
Copy link
Closed as not planned
Description
Feature hasn't been suggested before.
- 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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels

Activity
github-actions commentedon Dec 11, 2025
This feature request appears to be unique and doesn't duplicate existing issues. However, it's related to several ongoing plugin system improvements:
installhook #4726 - Custom toolsinstallhook (initialization hooks for tools)These issues are complementary to your SessionStart hook proposal and might benefit from coordinated implementation.
Brandtweary commentedon Dec 14, 2025
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.
arawak commentedon Mar 17, 2026
I implemented this on my branch and am opening a PR for it.
My main motivation was using the context-mode plugin with OpenCode so it can restore one-shot session context automatically instead of requiring manual setup each time.
The implementation adds
session.startwithstartup,resume, andcompacttriggers, stores pending hook context on the session, and injects it into the next LLM turn. I also added tests for the core hook behavior, resume wiring, and the fork startup timing edge case.One tradeoff in the current implementation: pending context is consumed lazily on the next model turn, so resume-triggered context can become stale if a session is resumed but not prompted soon after. I documented that in code/comments rather than broadening the scope of this PR.
c0dn commentedon Apr 4, 2026
Just wanted to add some context from a real use case — I'm building opencode-cache-guard, a plugin that warns when Anthropic prompt cache has expired on a stale session. The gap this issue describes is exactly what's blocking the ideal UX (warn on session load rather than on send).
#18007 looks like a solid implementation of this. It's drifted from
devdue to the Effect migration but the conflicts seem mechanical. Happy to help with a rebase if that's useful.ps: sorry for the tag, won't happen again
chore(release): v5.13.5 - axiom v0.6.2 pin + numeric sync
feat(agent): add opencode adapter + activity plugin hooks
feat(agent): opencode adapter + activity plugin hooks (#80)
guillempuche commentedon Jun 20, 2026
Use case:
session.createdruns a script that provisions a git worktree's Postgres database and MinIO bucket if they don't exist yet — works well for new sessions. The gap is--continue: resuming an existing session fires nothing, so there's no place to print a "worktree already provisioned, runpnpm dev" reminder on resume. Asession.resumedevent or atype: "new" | "resumed"field onsession.createdwould close this.Reference:
worktree-up.shExtend bg-sync to Codex CLI and Gemini CLI
cassiodias commentedon Jun 28, 2026
+1 on a native
session.starthook with asourcematcher. Adding a data point from running the plugin workaround in production.I ship a
session.createdplugin that auto-bootstraps session context (runs a setup script, surfaces a short status toast, injects a one-line primer for the model). It works well for fresh starts, and a couple of findings that might help scope this:session.createdfires on the first real interaction, not at TUI launch. Bootstrapping on launch alone misses it; subscribing tosession.createdis the right trigger.session.createdtwice for one new session. A module-levelSetof seen session ids keyed onevent.properties.info.iddedupes it; without that guard, side-effecting bootstrap runs twice. A native hook with a documented at-most-once contract would remove this footgun.--continue/--resume//resumeproduce no bus event, so a resumed session silently skips bootstrap and the context that startup loaded is absent for the rest of that session. There is no plugin-side workaround today --session.updatedis too noisy and carries no "this is a resume" signal. This is the one case the plugin pattern genuinely cannot cover.For the matcher, the three sources in the proposal (
startup/resume/compact) map cleanly onto how the lifecycle already behaves --compactoverlaps with the existingsession.compactedevent, andstartupoverlaps withsession.created, so the net-new capability the native hook unlocks is reallyresume. Even just emitting a bus event on resume (without the fullsession.starthook) would close the gap for plugin authors.Generic skeleton showing the
session.createdhandler + the at-most-once dedupe guard: https://gist.github.com/cassiodias/83a56ad40e1add6b95ab9b7dc9d99963feat: redesign orbit context around purpose — startup / cruise blocks
feat: redesign orbit context around purpose — startup / cruise blocks
feat: redesign orbit context around purpose — startup / cruise blocks
feat: redesign orbit context around purpose — startup / cruise blocks (…
github-actions commentedon Aug 28, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
fix(#116): follow opencode /new and session switches, and Claude forks
feat(agent): opencode adapter + activity plugin hooks (OrchestratorIn…