Feature Request: Plugin Lifecycle Hooks (Install/Update/Uninstall events)
Summary
Add hook events that fire during plugin lifecycle transitions — specifically PluginInstall, PluginUpdate, and PluginUninstall — so plugins can check migration needs, inform users about changes, and execute upgrade workflows at the right moment.
Motivation
The current hook system covers session-level and tool-level events (SessionStart, PreToolUse, Stop, etc.) but has no events tied to the plugin lifecycle itself. This creates several problems:
1. No way to check migration needs at install time
Plugins that evolve their schema or config format have no hook that fires when a new version is installed. Workarounds require polling on every SessionStart, which means migration checks run even when nothing has changed — and don't run when the change actually occurs (at install/update time).
2. No way to inform the user about the nature of changes
When a plugin is updated, there's no event where the plugin can surface a changelog, breaking-change warnings, or required manual steps. The best available option is a SessionStart banner — which the user sees next session, not at the moment of update.
3. No way to execute migrations automatically or by user choice
Plugins currently cannot trigger their own commands from hooks. Even if a SessionStart hook detects an update, it can only print text — it cannot invoke /plugin:update or run a migration script. The user must manually run the migration command.
4. No attended/unattended workflow distinction
There's no way for a lifecycle hook to know whether the session is interactive (user present) or running in a non-interactive/headless mode. This matters because some migrations require user confirmation (attended) while others should proceed automatically (unattended).
Concrete Example: The additionalContext Invisibility Problem
To illustrate problems #2 and #3, here is how Forge (an SDLC plugin) currently works around the lack of lifecycle hooks:
Forge uses a SessionStart command hook (check-update.js) that:
- Compares the installed version against the remote
plugin.json - Emits a JSON message via stdout:
{"additionalContext":"Forge 0.9.15 available (you have 0.9.14). Run /forge:update to review changes and update."} - This
additionalContextis injected into Claude's internal context — invisible to the user
The actual user experience:
- User updates the plugin (or it auto-updates)
- Next session, the hook detects the version change
- The update message is buried in session context — the user sees nothing
- No visible banner, no notification, no prompt to act
- The update sits there indefinitely until the user happens to type
/forge:update
The green "plugin loaded" banner in the corner only confirms the plugin is active — it does not convey update information.
So the current flow is:
SessionStart → hook detects update → injects invisible context → user sees nothing → update doesn't happen
A PluginUpdate lifecycle hook would make this:
PluginUpdate fires → user sees visible notification with changelog → user chooses to run migration → update completes
Current Workarounds (and their limitations)
Forge uses a SessionStart command hook to:
- Compare installed vs. remote
plugin.jsonversions - Detect distribution switches (forge@forge ↔ forge@skillforge)
- Emit
additionalContextwith update information
This works, but:
- The check runs on every session start (wasteful when no update exists)
- The user doesn't see the update notification because
additionalContextis internal - The plugin cannot auto-execute migrations or present an interactive choice
- The user must independently discover and remember to run
/forge:update
Proposed Design
New Hook Events
PluginInstall — fires when a plugin is first installed
PluginUpdate — fires when an installed plugin is updated to a new version
PluginUninstall — fires when a plugin is removed
Hook Input (event-specific fields)
{
"hook_event_name": "PluginUpdate",
"plugin_id": "forge:forge",
"plugin_name": "Forge",
"old_version": "0.9.13",
"new_version": "0.9.14",
"migrations_url": "https://raw.githubusercontent.com/.../migrations.json",
"interactive": true
}
Key additions:
old_version/new_version— so the hook knows what changedmigrations_url— fromplugin.json, already declared by many pluginsinteractive—trueif a user is present (attended),falseif headless (unattended)
Hook Output (for PluginUpdate)
{
"decision": "proceed | block",
"reason": "Migration requires manual step: run /forge:migrate-config",
"systemMessage": "Forge updated from 0.9.13 → 0.9.14. Breaking change: persona schema v2 required.",
"auto_commands": ["/forge:update"]
}
Key additions:
auto_commands— list of slash commands the hook requests to run after the lifecycle event completesdecision— allow or block the update (for pre-checks that find incompatibilities)
Attended vs. Unattended Behavior
When interactive: true:
- The user sees the hook's
systemMessageandreasonin the transcript auto_commandsare presented as suggestions the user can accept or skip
When interactive: false (headless/CI):
- The hook's
systemMessageis logged auto_commandsrun automatically unlessdecision: "block"- If
decision: "block", the update is skipped with the reason logged
Use Cases
| Use Case | How It Works |
|---|---|
| Schema migration on update | PluginUpdate hook reads migrations_url, determines steps needed, runs them |
| Breaking-change notification | PluginUpdate hook returns systemMessage with changelog summary |
| First-install setup | PluginInstall hook creates config files, sets up .forge/ directory |
| Cleanup on removal | PluginUninstall hook removes generated files (opt-in) |
| CI/CD safe updates | interactive: false + auto_commands for unattended migration |
Impact
This feature would benefit every plugin that manages project state (configs, schemas, generated files) — which is a large and growing category. Currently, all such plugins must reinvent the "detect update → inform user → run migration" pattern using SessionStart polling, which is fragile, wasteful, and invisible to the user.
Adding lifecycle hooks would make plugin updates as seamless as npm install with postinstall scripts, while preserving user control through the interactive flag and decision output.
Environment
- Claude Code version: latest
- OS: all
- Related: Forge plugin (https://github.com/Entelligentsia/forge)
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗