Symptom in cargo-hauler
ScriptedAlchemy/cargo-hauler#48 — the installed Claude pack is refused by Claude Code (Status: ✘ failed to load, errors: ["Hook load failed: Duplicate hooks file detected …"] in claude plugin list --json), yet the framework's own tooling reported it healthy: cargo-hauler PR 34's install proof records agent-bundle install claude --from artifact/claude --scope user → state: installed and doctor --host claude → installed copy: current. The user only found out by running claude plugin list --json | jq '.errors' by hand.
The second ask in that issue is exactly this: have the install/verify path surface errors[] so a plugin that failed to load is reported as broken, not current.
Root cause in agent-bundle (main 10a98a0fb)
Both readers of claude plugin list --json only look at id, installPath, scope, version and treat every row as state: 'installed':
packages/agent-bundle/src/install/doctor.ts:874-893 (publicHostInventory, Claude branch) — builds DoctorFinding { state: 'installed', … } per row; the row's errors array (and any enabled/failed status field) is never read, so the installed-copy comparison proceeds to current.
packages/agent-bundle/src/install/install.ts:305-328 (parsePublicHostInventory) — same fields; install prints state: installed from this even when the host has rejected the plugin.
- The Codex branch (
doctor.ts:894-912) skips rows with installed === false but likewise ignores any error payload.
The capabilities table already knows the surface: capabilities/claude-2.1.250.json:839 records that claude plugin list --json exposes error codes (dependency-unsatisfied, range-conflict, …) in errors.
Workaround currently in cargo-hauler
None; docs/install.md tells users to trust agent-bundle doctor --host <host>.
Proposed fix
- Parse
errors (array of strings) on Claude rows and the equivalent failure/enabled fields on Codex rows in one shared reader (install.ts parsePublicHostInventory is already shared by install and doctor).
- doctor: when the matching row has a non-empty
errors, emit an error-severity diagnostic (new code in the AB73xx install/doctor family) naming the host's message verbatim, and report the installed copy as broken rather than current/stale. The JSON output carries errors on the finding.
- install: after
claude plugin install, re-read the inventory and fail (non-zero, state: 'failed-to-load' or similar) when the freshly installed row reports errors, instead of state: installed.
- Docs:
website/docs/{en,zh} doctor/install pages and docs/diagnostics.md gain the new code.
Acceptance
- Fixture: a
claude plugin list --json payload with errors: ["Hook load failed: …"] → agent-bundle doctor --host claude exits non-zero, prints the message, and the JSON report's installed copy state is not current.
agent-bundle install claude … against a plugin Claude refuses (e.g. today's duplicate-hooks manifest) exits non-zero and names the error.
- No change in output for a healthy install (
errors: []).
Symptom in cargo-hauler
ScriptedAlchemy/cargo-hauler#48 — the installed Claude pack is refused by Claude Code (
Status: ✘ failed to load,errors: ["Hook load failed: Duplicate hooks file detected …"]inclaude plugin list --json), yet the framework's own tooling reported it healthy: cargo-hauler PR 34's install proof recordsagent-bundle install claude --from artifact/claude --scope user → state: installedanddoctor --host claude → installed copy: current. The user only found out by runningclaude plugin list --json | jq '.errors'by hand.The second ask in that issue is exactly this: have the install/verify path surface
errors[]so a plugin that failed to load is reported as broken, notcurrent.Root cause in agent-bundle (main
10a98a0fb)Both readers of
claude plugin list --jsononly look atid,installPath,scope,versionand treat every row asstate: 'installed':packages/agent-bundle/src/install/doctor.ts:874-893(publicHostInventory, Claude branch) — buildsDoctorFinding { state: 'installed', … }per row; the row'serrorsarray (and any enabled/failed status field) is never read, so the installed-copy comparison proceeds tocurrent.packages/agent-bundle/src/install/install.ts:305-328(parsePublicHostInventory) — same fields;installprintsstate: installedfrom this even when the host has rejected the plugin.doctor.ts:894-912) skips rows withinstalled === falsebut likewise ignores any error payload.The capabilities table already knows the surface:
capabilities/claude-2.1.250.json:839records thatclaude plugin list --jsonexposes error codes (dependency-unsatisfied, range-conflict, …) inerrors.Workaround currently in cargo-hauler
None;
docs/install.mdtells users to trustagent-bundle doctor --host <host>.Proposed fix
errors(array of strings) on Claude rows and the equivalent failure/enabled fields on Codex rows in one shared reader (install.tsparsePublicHostInventoryis already shared by install and doctor).errors, emit anerror-severity diagnostic (new code in the AB73xx install/doctor family) naming the host's message verbatim, and report the installed copy asbrokenrather thancurrent/stale. The JSON output carrieserrorson the finding.claude plugin install, re-read the inventory and fail (non-zero,state: 'failed-to-load'or similar) when the freshly installed row reports errors, instead ofstate: installed.website/docs/{en,zh}doctor/install pages anddocs/diagnostics.mdgain the new code.Acceptance
claude plugin list --jsonpayload witherrors: ["Hook load failed: …"]→agent-bundle doctor --host claudeexits non-zero, prints the message, and the JSON report's installed copy state is notcurrent.agent-bundle install claude …against a plugin Claude refuses (e.g. today's duplicate-hooks manifest) exits non-zero and names the error.errors: []).