Feature Request: Plugin Lifecycle Hooks (Install/Update/Uninstall events)

Resolved 💬 6 comments Opened Apr 16, 2026 by devasur Closed Jun 25, 2026

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:

  1. Compares the installed version against the remote plugin.json
  2. 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."}
  3. This additionalContext is 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.json versions
  • Detect distribution switches (forge@forge ↔ forge@skillforge)
  • Emit additionalContext with 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 additionalContext is 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 changed
  • migrations_url — from plugin.json, already declared by many plugins
  • interactivetrue if a user is present (attended), false if 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 completes
  • decision — allow or block the update (for pre-checks that find incompatibilities)

Attended vs. Unattended Behavior

When interactive: true:

  • The user sees the hook's systemMessage and reason in the transcript
  • auto_commands are presented as suggestions the user can accept or skip

When interactive: false (headless/CI):

  • The hook's systemMessage is logged
  • auto_commands run automatically unless decision: "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

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗