Skip to content

fix: improve plugin system robustness — agent/command resolution, async errors, hook timing, two-phase init#18280

Open
ryanskidmore wants to merge 1 commit intoanomalyco:devfrom
ryanskidmore:fix/plugin-system-robustness
Open

fix: improve plugin system robustness — agent/command resolution, async errors, hook timing, two-phase init#18280
ryanskidmore wants to merge 1 commit intoanomalyco:devfrom
ryanskidmore:fix/plugin-system-robustness

Conversation

@ryanskidmore
Copy link
Contributor

@ryanskidmore ryanskidmore commented Mar 19, 2026

Issue for this PR

Closes #18310

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Agent.get() and Command.get() return undefined for unknown names, but five call sites in prompt.ts dereference the result without null checks — producing a raw TypeError instead of a useful error. The prompt_async route drops errors silently because the detached SessionPrompt.prompt() call has no .catch(). Plugin config hooks aren't error-isolated, so one throwing hook kills the rest. The command.execute.before hook fires after template parts are merged with caller parts, making clean template replacement fragile. And plugins added by a config hook aren't loaded in the same startup pass because Plugin.state() is already cached.

Fixes:

  1. Agent/command null guards — Added null checks at all five Agent.get()/Command.get() call sites (createUserMessage, loop normal + subtask, shell, command). On missing agent/command, throws NamedError.Unknown with the list of available names and publishes Session.Event.Error. Follows the existing guard pattern already at line 1858 of prompt.ts. Empty-string agent falls back to Agent.defaultAgent() instead of looking up "".

  2. prompt_async error handling — Added .catch() on the detached SessionPrompt.prompt() call in the prompt_async route that logs the error and publishes Session.Event.Error. The route still returns 204 immediately — the fire-and-forget semantics are preserved, errors are just no longer invisible.

  3. Config hook error isolation — Wrapped each individual hook.config?.() call in Plugin.init() with try/catch so a failing hook is logged but doesn't prevent others from running.

  4. Command hook timing — Moved Plugin.trigger("command.execute.before") to fire before template parts are merged with input.parts. Plugins now receive template-only parts in output.parts and can replace template content cleanly. input.parts are merged after the hook returns. Subtask path unchanged. No hook type signature change.

  5. Two-phase plugin init — After config hooks fire in Plugin.init(), the code now diffs config.plugin against what was originally loaded, imports any newly-added plugins using the same install/import path as state(), runs their config hooks once, and pushes them into the shared hooks array. One extra pass only — no recursive loading.

How did you verify your code works?

7 new test cases added to existing test files, all passing:

  • test/session/prompt.test.ts — unknown agent throws NamedError.Unknown (not TypeError), error includes available names, empty agent falls back to default, unknown command throws with names; command hook receives template-only parts before input merge
  • test/server/session-messages.test.ts — structural test verifying .catch() + Bus.publish(Session.Event.Error) on prompt_async route
  • test/plugin/auth-override.test.ts — two-phase plugin loading structure verified, config hooks individually error-isolated with try/catch

bun typecheck clean across all 13 packages. bun test passes.

Screenshots / recordings

N/A — no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions github-actions bot added needs:compliance This means the issue will auto-close after 2 hours. contributor and removed needs:compliance This means the issue will auto-close after 2 hours. labels Mar 19, 2026
@github-actions
Copy link
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@ryanskidmore ryanskidmore force-pushed the fix/plugin-system-robustness branch from d8866cb to cc07261 Compare March 19, 2026 23:44
…nc error handling, hook timing, and two-phase init

- Guard against undefined Agent.get()/Command.get() at all call sites with
  typed NamedError and available-name hints instead of silent TypeErrors
- Add .catch() on prompt_async route to surface detached prompt failures
  via Session.Event.Error instead of silently swallowing them
- Isolate plugin config hook errors with per-hook try/catch so one failing
  hook no longer kills the entire bootstrap
- Move command.execute.before hook to fire before template parts are merged
  with input parts, giving plugins access to raw template content
- Add two-phase plugin initialization so plugins injected by config hooks
  are loaded and initialized in the same startup pass
- Add tests for agent/command resolution errors, async error handling,
  command hook timing, and plugin config hook ordering
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin system: undefined agent/command crashes, silent prompt_async errors, config hook isolation, hook timing, startup ordering

1 participant