Plugin hook system: add delegate response format so plugins can generate AI reactions via Claude Code's own session

Resolved 💬 3 comments Opened Apr 4, 2026 by lucianfialho Closed May 15, 2026

Summary

Today, Claude Code plugins that want to generate AI-driven responses (e.g., buddy reactions, custom assistant behaviors) must make independent Anthropic API calls using their own ANTHROPIC_API_KEY. This creates a hard dependency that breaks the plugin's value proposition: users who install a third-party buddy plugin are forced to also own and expose a developer API key, even though they already have a Claude Code subscription.

This issue proposes a delegate response format for hook handlers that lets a plugin describe what to generate while Claude Code performs the actual LLM call using its own authenticated session.

---

Current Flow (requires ANTHROPIC_API_KEY)

1. CC fires PostToolUse hook → spawns plugin process
2. Plugin receives context (file, tool, output) via stdin
3. Plugin calls Anthropic API directly → needs ANTHROPIC_API_KEY
4. Plugin writes reaction text to stdout
5. CC displays the reaction

Problem: step 3 requires the plugin to have its own API key. Claude Code's internal OAuth token (sk-ant-oat01-..., stored in macOS keychain as Claude Code-credentials) is scoped to the claude.ai web API and returns 401 {"message":"OAuth authentication is currently not supported."} when used against the developer API.

---

Proposed Flow (delegate to CC session)

1. CC fires PostToolUse hook → spawns plugin process
2. Plugin receives context via stdin
3. Plugin evaluates condition locally (no LLM, no API key needed)
4. Plugin writes a delegate response to stdout:
   {"delegate": true, "systemPrompt": "...", "prompt": "...", "maxTokens": 150}
5. CC performs the LLM call using its own session
6. CC displays the response as a buddy reaction

The plugin never touches auth. It only defines the prompt.

---

Proposed Protocol

A hook handler can return one of three shapes:

// 1. Static reaction (already works today)
{"reaction": "Build failed again. Shocking."}

// 2. Delegate reaction (proposed)
{
  "delegate": true,
  "systemPrompt": "You are GitBot, a sarcastic git companion. Keep responses under 2 sentences.",
  "prompt": "Developer ran a destructive git operation. React as GitBot.",
  "maxTokens": 120,
  "model": "claude-haiku-4-5-20251001"
}

// 3. No reaction (already works — return nothing or exit 0)

CC processes the delegate response, calls its internal LLM client with the provided system prompt and prompt, and surfaces the result as a buddy speech bubble (or whatever the hook's output channel is).

---

Real-World Use Case: buddy-sdk

We built buddy-sdk — an open-source SDK that lets developers create Claude Code companion plugins (specialized AI assistants with tamagotchi identity). Here's what a plugin looks like today:

// buddy.config.ts (using buddy-sdk)
export default defineBuddy({
  name: 'GitBot',
  personality: 'sarcastic senior dev who has seen too many git disasters',
  systemPrompt: 'You are GitBot. Keep reactions under 2 sentences. No markdown.',

  hooks: {
    'bash:after': {
      // Condition runs locally — no API key needed
      condition: (ctx) => /\bgit\b/.test(ctx.toolInput?.command ?? ''),

      // This currently requires ANTHROPIC_API_KEY
      // With delegate support it would emit the delegate format instead
      react: async (ctx) => ctx.claude(
        `Developer ran: ${ctx.toolInput.command}\nReact as GitBot.`
      ),
    },
  },
})

With delegate support, ctx.claude() would emit the delegate format instead of making a direct API call — zero auth config required from the plugin author or the user.

Companies building branded companions (e.g., an eslint-buddy published by the ESLint team) shouldn't need to manage API keys or token rotation. The plugin defines the personality; CC provides the intelligence.

---

Why This Matters for the Plugin Ecosystem

The /buddy feature ships with Claude Code and generates reactions using CC's own session. Third-party plugins should have the same capability. Without it:

  • Plugin authors must host their own API key infrastructure
  • Users must expose developer API keys in their environment
  • The value prop of "install a buddy plugin" becomes "install a buddy plugin AND get an API key AND set env vars"

This is the same asymmetry that would exist if VS Code extensions couldn't access the user's GitHub token and had to create their own OAuth app.

---

Implementation Sketch

On the Claude Code side, the change is localized to the hook response processor:

// Pseudocode — hook response handler in CC
async function processHookResponse(response: HookResponse, session: CCSession) {
  if (response.delegate) {
    const text = await session.complete({
      system: response.systemPrompt,
      prompt: response.prompt,
      maxTokens: response.maxTokens ?? 150,
      model: response.model ?? session.defaultModel,
    })
    return { reaction: text }
  }
  return response
}

No new auth surface is exposed. The plugin process never receives a token — it only sends a prompt shape. CC validates and rate-limits as it would any internal call.

---

Security Considerations

  • Plugin process sends a prompt, not a token. No credential leakage.
  • CC can apply the same content policy it applies to its own prompts.
  • CC can enforce maxTokens caps per plugin to prevent abuse.
  • Opt-in: only hooks that return {"delegate": true, ...} trigger this path. Existing hooks are unaffected.

---

Related Issues

  • #43244 — BuddyReact hook event for companion speech capture
  • #41915 — Add locale parameter to buddy_react API
  • #42091 — Would love to be able to create buddies kind of like sub-agents
  • #41929 — Allow /buddy species customization

---

Alternatives Considered

| Alternative | Problem |
|---|---|
| Expose CC's OAuth token to plugins | Token is scoped to claude.ai web API, not developer API. Also a security risk. |
| CC runs a localhost proxy plugins can call | Requires service discovery, port management, and still exposes auth surface |
| Plugins bundle their own LLM client | Forces API key requirement — exactly the problem we're solving |
| Only native CC buddies get LLM access | Closes the ecosystem to third-party plugin authors |

---

The delegate format is the minimal, secure, and composable solution. It keeps auth entirely inside CC while unlocking a plugin ecosystem that can build on top of CC's intelligence rather than around it.

View original on GitHub ↗

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