claude-code - 💡(How to fix) Fix Feature Request: Plugin Lifecycle Hooks (Install/Update/Uninstall events) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#48986Fetched 2026-04-17 08:53:58
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
labeled ×3commented ×1

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.

Root Cause

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).

Fix Action

Fix / Workaround

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).

Current Workarounds (and their limitations)

Code Example

SessionStart → hook detects update → injects invisible context → user sees nothing → update doesn't happen

---

PluginUpdate fires → user sees visible notification with changelog → user chooses to run migration → update completes

---

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_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
}

---

{
  "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"]
}
RAW_BUFFERClick to expand / collapse

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 CaseHow It Works
Schema migration on updatePluginUpdate hook reads migrations_url, determines steps needed, runs them
Breaking-change notificationPluginUpdate hook returns systemMessage with changelog summary
First-install setupPluginInstall hook creates config files, sets up .forge/ directory
Cleanup on removalPluginUninstall hook removes generated files (opt-in)
CI/CD safe updatesinteractive: 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

extent analysis

TL;DR

Implementing PluginInstall, PluginUpdate, and PluginUninstall lifecycle hooks with specific input and output formats will enable plugins to handle migrations, inform users about changes, and execute upgrade workflows seamlessly.

Guidance

  1. Define the hook events: Introduce PluginInstall, PluginUpdate, and PluginUninstall events to cover the entire plugin lifecycle.
  2. Specify hook input: Design a standardized input format for each hook event, including fields like plugin_id, old_version, new_version, migrations_url, and interactive.
  3. Determine hook output: Establish a consistent output format for each hook, such as decision, reason, systemMessage, and auto_commands, to facilitate plugin updates and user interactions.
  4. Handle attended and unattended modes: Implement logic to distinguish between interactive and headless sessions, affecting how hook output is presented to the user or executed automatically.

Example

// Hook input example for PluginUpdate
{
  "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
}

// Hook output example for PluginUpdate
{
  "decision": "proceed",
  "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"]
}

Notes

The proposed design assumes that the migrations_url field in plugin.json is already utilized by many plugins, and that the interactive flag can reliably indicate whether a session is interactive or headless.

Recommendation

Apply the proposed design for PluginInstall, PluginUpdate, and PluginUninstall lifecycle hooks to enhance plugin management and user experience, as it provides a structured approach to handling plugin updates, migrations, and user interactions.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Feature Request: Plugin Lifecycle Hooks (Install/Update/Uninstall events) [1 comments, 2 participants]