Skip to content

fix: auto-resolve two bug reports — plugin reattach isolation (#496) + routed-model usage attribution (#495) - #511

Draft
TYRMars wants to merge 2 commits into
mainfrom
claude/vibrant-dijkstra-oaur8h
Draft

fix: auto-resolve two bug reports — plugin reattach isolation (#496) + routed-model usage attribution (#495)#511
TYRMars wants to merge 2 commits into
mainfrom
claude/vibrant-dijkstra-oaur8h

Conversation

@TYRMars

@TYRMars TYRMars commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Auto-resolves two independent bug reports, one commit each.


#496 — plugin reattach: one bad SKILL.md silently kills a plugin's MCP servers + sibling skills

PluginManager.#reattachOne iterated a plugin's skills with no per-skill error guard, and the MCP-server loop ran after it. On server restart, one unreadable/unparseable SKILL.md threw and aborted the rest of that plugin's reattach — silently killing every MCP server it declared plus all sibling skills — while reattachInstalled's bare .catch(() => {}) swallowed the error and GET /v1/plugins kept reporting the plugin as fully installed (that data comes from the ledger, not live state).

Fix (packages/plugin/src/manager.ts):

  • Per-skill try/catch around each skill's load/parse/insert, so one bad file can't take down siblings or MCP servers (matches SkillCatalog.scanRoot and the MCP loop's existing best-effort pattern).
  • MCP add failures recorded rather than silently swallowed.
  • New degraded / last_error fields on the ledger entry, set at reattach time and cleared on a clean reattach, surfaced through list() / get() so GET /v1/plugins stops reporting a half-attached plugin as healthy.
  • reattachInstalled records whole-plugin failures (e.g. corrupt plugin.json) on the entry.
  • Web-side InstalledPlugin type gains the two optional fields.

Tests: a corrupted installed SKILL.md is skipped while its sibling skill and the MCP server still come up and the entry is marked degraded; a subsequent clean reattach clears the marker.


#495 — routed usage priced against the wrong model under JARVIS_ROUTER_ENABLED

The agent loop stamped every usage event with the static config.model, not the model the request actually ran on. With routing enabled, RoutingProvider rewrites the model before delegating, so all token usage from routed turns was attributed to the pre-routing model and the web UsagePanel priced it from a table keyed by that wrong name — under-reporting cost by up to two orders of magnitude, invisibly (the expensive tier showed zero usage; the "unknown model → partial estimate" safety valve never fired because the stale name is a valid known one).

Fix (packages/core, packages/router):

  • Optional model added to the usage LlmChunk variant and to ChatResponse (absent for plain providers).
  • RoutingProvider tags the streamed usage chunk and the blocking response with the resolved tier target, using ?? existing so a downstream that reports its own model wins.
  • agent.ts prefers chunk.model ?? this.config.model — non-routing providers unchanged, routed usage attributed correctly.

Tests: streamed + blocking routed usage carries the tier-target model; a downstream-set model is not overwritten.


Verification

  • pnpm --filter @jarvis/plugin test → 30/30; @jarvis/router → 27/27; @jarvis/core → 30/30. Lint clean on all changed files.
  • Pre-existing, unrelated failures in this environment are not touched: server tests needing native pty.node / an unbuilt UI dist, and a latent assert.ok type-narrowing error in router.test.ts's chain-reconciliation test (present on main, identical before/after this change).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pdj9CWcTPhxPMi559vJpDz

claude added 2 commits July 22, 2026 01:26
`PluginManager.#reattachOne` iterated a plugin's skills with no per-skill
error guard, and the MCP-server loop ran after it. One unreadable or
unparseable SKILL.md threw, aborting the rest of that plugin's reattach —
including every MCP server it declared — and `reattachInstalled`'s bare
`.catch(() => {})` swallowed the rejection, so an entire MCP integration
plus all sibling skills vanished silently on restart while
`GET /v1/plugins` kept reporting the plugin as fully installed.

- Wrap each skill's load/parse/insert in a per-skill try/catch that
  records the failure and continues, matching `SkillCatalog.scanRoot`
  and the MCP loop below it.
- Record (rather than silently swallow) per-MCP add failures too.
- Add `degraded` / `last_error` fields to the ledger entry, set at
  reattach time (and cleared on a clean reattach), surfaced through
  `list()` / `get()` so `GET /v1/plugins` no longer reports a
  half-attached plugin as healthy.
- `reattachInstalled` now records whole-plugin failures (e.g. a corrupt
  plugin.json) on the entry instead of dropping them on the floor.

Adds regression tests: one bad SKILL.md no longer takes down sibling
skills or the MCP server, the plugin is marked degraded, and a
subsequent clean reattach clears the marker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pdj9CWcTPhxPMi559vJpDz
…uting one (#495)

Under JARVIS_ROUTER_ENABLED, RoutingProvider rewrites ChatRequest.model
before delegating, but the agent loop stamped every `usage` event with
the static `config.model`. So all token usage from routed turns was
attributed to the pre-routing model, and the web UsagePanel priced it
from a table keyed by that wrong name — under-reporting cost by up to two
orders of magnitude with no anomaly visible (the expensive tier showed
zero usage, and the "unknown model → partial estimate" safety valve never
fired because the stale name is a valid known one).

Propagate the effective model back through the provider seam:
- Add an optional `model` to the `usage` LlmChunk variant and to
  ChatResponse (absent for plain providers).
- RoutingProvider tags the streamed usage chunk and the blocking response
  with the resolved tier target — using `?? existing` so a downstream that
  reports its own model is respected.
- agent.ts prefers `chunk.model ?? this.config.model`, so non-routing
  providers are unchanged while routed usage is attributed correctly.

Adds regression tests: streamed + blocking routed usage carries the tier
target model, and a downstream-set model is not overwritten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pdj9CWcTPhxPMi559vJpDz
@TYRMars TYRMars changed the title fix(plugin): guard per-skill reattach + surface degraded plugins (#496) fix: auto-resolve two bug reports — plugin reattach isolation (#496) + routed-model usage attribution (#495) Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants