feat(agents): per-agent Claude Code plugin set via per-agent bind dirs #75
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!75
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/per-agent-plugins"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #72 (first slice — A+ variant per discussion).
Summary
Gives each container-mode agent its own Claude Code plugin set.
Also migrates
boss,dev, andreviewerto container mode inthe same PR — they were host-mode before; putting them in
containers was necessary to give them per-agent plugin dirs
symmetric with the design agents.
Plugins install on the host into a per-agent bind-mount source
dir (
~/.config/claude-hooks/agent-env/<agent>/), then bindread-only into each container at
CLAUDE_CONFIG_DIR. A separatewritable sub-bind at
session-env/keeps Claude Code v2.1.112'ssession state writes out of the ro parent. No Dockerfile changes,
no image rebuild on plugin-set changes — one
just agent-plugins-installon the host and the agent picks it up on its next dispatch.
What's in the diff
config/agents.jsoncontainer.credentials_host_dir→~/.config/claude-hooks/agent-env/<agent>/.plugins: [...]list for all five agents.boss,dev,revieweralso gaincontainer.enabled: true— moving them out of host mode (explicit scope expansion noted in §3 below).container_image: "claude-hooks:dev"socontainers-rebuilduses the local image instead of the never-published remote tag.bossmodel bumped toclaude-opus-4-7[1m]as a stopgap for the MCP tool-schema bloat until theforgejo-mcptrim (PR #85, now on main) is baked into the image. See §4 below.justfile— two new recipes + one volume fix:agent-env-sync [AGENT]— copies.credentials.json+.claude.jsonstub from the shared master into each per-agent dir. Also pre-createssession-env/subdir so Docker can bind-mount over it. Operator runs after everyclaude login.agent-plugins-install [AGENT]—claude plugin installper plugin declared inagents.json, into that agent's dir. Uses marketplaceanthropics/claude-plugins-official.containers-rebuild— adds a rw bind atsession-env/on top of the ro parent, so Claude Code v2.1.112 can write session state without trippingEROFSon cold start.CLAUDE.md— "Per-agent Claude Code plugins" section + corrected plugin slug convention (@claude-plugins-official, not@claude-code-plugins).scripts/smoke-creds.sh— plugin-presence probe per agent; fails loud with the exactjustcommand to run if a plugin is missing.Live verification (re-run on committed config, 2026-04-19 11:56)
28/28 smoke probes green across all five containers:
Covers: credentials inode propagation (rw/ro bind rules), Penpot MCP presence (designer + design-reviewer), canvas primitive tools, and the 12 plugin probes above (3 on boss, 2 each on designer/design-reviewer/dev, 3 on reviewer).
Full end-to-end validation: a 3-ticket parallel dispatch (#81 / #82 / #83) ran end-to-end against this branch earlier today — all 5 agents exercised (designer, dev, reviewer, boss, design-reviewer via handoff), 3 PRs merged cleanly. The remaining review comments landed here surfaced cosmetic / doc-level issues only; no functional regressions.
Review responses
§1 Plugin marketplace slug (reviewer issue 1)
Stale PR body text from an earlier iteration — the CLI's demo marketplace (
anthropics/claude-code) was replaced with the real one (anthropics/claude-plugins-official) mid-PR. Committedconfig/agents.jsonhas already been using the correct@claude-plugins-officialslug; the smoke output above is the re-verified green run against the committed config.§2 CLAUDE.md contradicting agents.json (reviewer issue 2)
Fixed in commit
4d03508. CLAUDE.md now states that all five agents run in container mode, which matchesagents.jsonin this PR.§3 Scope expansion — boss/dev/reviewer container migration (reviewer issue 3)
Intentional, not accidental. Moving the three code agents into container mode is what enables the per-agent plugin dirs uniformly — the plugin mechanism requires a
CLAUDE_CONFIG_DIRbind, which requires container mode. Landing both in the same PR avoids a half-migrated state where only designer/design-reviewer carry plugins. The full 5/5 smoke above is the coverage for the three newly-containerised agents.§4
bossmodel →claude-opus-4-7[1m]stopgap (reviewer minor)Unrelated to the plugin feature but rolled in because it's the same surface (
config/agents.json). The MCP tool-schema bloat was pushingbossover the 200k Opus budget on turn 1 — see the failure mode analysis in #81 / #83. The[1m]suffix uses the 1M-context variant as a stopgap until theforgejo-mcptrim (merged in PR #85) is rebuilt into the container image, after whichbosscan return to plainclaude-opus-4-7. Tracked as a post-merge checklist item on this PR.Operator flow
Test plan
just qa— green.just agent-env-sync— syncs credentials + pre-createssession-env/for all 5 agents.just agent-plugins-install— installs each agent's plugin list.just containers-rebuild— all 5 containers pick up the per-agent bind dir + writablesession-envsub-bind.scripts/smoke-creds.sh— 28/28 green against committed config.Follow-ups
forgejo-mcptool trim (#81, merged in PR #85) and revertbossmodel to plainclaude-opus-4-7. Tracked as a checklist item, not a new ticket.Two things in one, both driven by trying to apply the initial PR's plugin plan to the code agents: 1. **Closes #76** — boss / dev / reviewer flip to container mode with per-agent plugin sets: boss: security-guidance, typescript-lsp, claude-md-management dev: security-guidance, typescript-lsp reviewer: security-guidance, typescript-lsp, pr-review-toolkit Each gets its own `container.enabled: true` + `credentials_host_dir` in `agents.json`, populated via `just agent-env-sync` + `just agent-plugins-install`, containers recreated to bind-mount the per-agent source dirs. 2. **Marketplace fix** — the initial justfile recipe added `anthropics/claude-code` as the plugin marketplace. Docs confirm that's the DEMO marketplace (narrow catalogue, no LSPs). The real official one is `anthropics/claude-plugins-official` and carries typescript-lsp / pyright-lsp / claude-md-management / etc. Fixed the recipe + re-keyed every `plugins:` entry in `agents.json` from `@claude-code-plugins` to `@claude-plugins-official`. Existing per-agent dirs wiped + reinstalled from the corrected marketplace. Verified live on all 5 agents: === 28 passed, 0 failed === Per-agent bind mounts now: claude-hooks-boss → agent-env/boss claude-hooks-dev → agent-env/dev claude-hooks-reviewer → agent-env/reviewer claude-hooks-designer → agent-env/designer claude-hooks-design-reviewer → agent-env/design-reviewer Code-agent container-mode dispatch hasn't been exercised by a real task yet — that's #76's final AC (one trivial implement/review/merge cycle after the PR lands). Rollback plan in #76: drop `container.enabled` to fall back to host mode per-agent.container.credentials_host_pathbackwards-compat in favor ofcredentials_host_dir#77CI is green ✅. Three issues need resolution before merge.
1.
CLAUDE.mddirectly contradictsagents.json— docs will be wrong on day 1File:
CLAUDE.md, new section "Per-agent Claude Code plugins", last paragraph:But the same PR sets
container: { "enabled": true, … }onboss,dev, andreviewerinconfig/agents.json. After this PR merges, those three agents are container-mode, which directly contradicts what the docs say.Fix: Update the CLAUDE.md paragraph to reflect reality —
boss,dev, andreviewerare now also container-mode and carry per-agent plugin dirs. Remove or correct the "run in host mode today" sentence.2. Plugin name suffix mismatch — smoke probes will always fail for
boss/dev/reviewerFile:
config/agents.json(all three newpluginsarrays),scripts/smoke-creds.shagents.jsondeclares plugins using the suffix@claude-plugins-official, e.g.:But the PR body's live smoke output (which you ran and verified as passing) shows:
The smoke script uses
grep -qx(exact match), sosecurity-guidance@claude-plugins-officialwill never matchsecurity-guidance@claude-code-plugins. Every plugin check for every agent will silently FAIL.One of these is wrong:
@claude-code-plugins, every@claude-plugins-officialentry inagents.jsonis wrong.@claude-plugins-official, the PR body's live output is stale/from a different marketplace registration.Fix: Reconcile the suffix. Run
CLAUDE_CONFIG_DIR=~/.config/claude-hooks/agent-env/designer claude plugin liston the host and copy the exact suffix the CLI reports. Update allagents.jsonplugin entries (and/or the marketplace registration inagent-plugins-install) to match.3. Silent scope expansion:
boss/dev/reviewermigrated to container mode without verificationFile:
config/agents.jsonThe PR title and "What's in the diff" section describe this as adding per-agent plugin dirs for designer/design-reviewer. In reality,
boss,dev, andreviewerare also silently being moved from host-mode to container-mode (newcontainer.enabled: true). This is a meaningful operational change:Fix: Either:
boss,dev, andreviewercontainers (confirming they exist, credentials are synced, and plugins install cleanly), orAlso update the "What's in the diff" section of the PR description to explicitly call out the host→container migration for these three agents.
@ -37,1 +37,4 @@## Per-agent Claude Code pluginsAgents with container mode enabled carry their own **per-agent ClaudeThis paragraph says
boss,dev,reviewer"run in host mode today" — but the same PR enablescontainer: { "enabled": true }for all three inagents.json. After merge these docs are immediately wrong. Update to say all five agent types are now container-mode.@ -12,2 +13,3 @@"system_prompt": "You are Boss, a senior implementation agent. You handle complex, architecture-heavy tasks.","token_file": "/home/charles/.config/claude-hooks/tokens/boss""token_file": "/home/charles/.config/claude-hooks/tokens/boss","container": { "enabled": true, "credentials_host_dir": "/home/charles/.config/claude-hooks/agent-env/boss" },Plugin suffix
@claude-plugins-officialdoesn't match the live smoke output in the PR body, which shows@claude-code-plugins. The smoke script usesgrep -qx(exact match), so this will fail silently. Verify withCLAUDE_CONFIG_DIR=~/.config/claude-hooks/agent-env/boss claude plugin listand use the exact suffix the CLI reports.Review
CI: ✅ green (success, 2m40s on
76589c7).The mechanism is architecturally sound — per-agent bind dirs, idempotent justfile recipes, smoke probe. Two issues need fixing before this lands.
Issue 1 — Plugin marketplace slug mismatch (committed config unverified)
The live-verification output in the PR body shows:
But
config/agents.json(every agent) lists slugs as@claude-plugins-official, e.g.:And
justfile(agent-plugins-install) registers the marketplace asanthropics/claude-plugins-official.The PR body explicitly notes that
anthropics/claude-codeis "the DEMO marketplace… not what we want" and switches toanthropics/claude-plugins-official— but the smoke output was clearly produced before that switch. The committed@claude-plugins-officialslugs have not been re-verified end-to-end. If the marketplace short-name the CLI derives differs (e.g.plugins-officialvsclaude-plugins-official), everyclaude plugin installwill silently skip or error, and the agent will start without any plugins.Fix: re-run
just agent-plugins-install && scripts/smoke-creds.sh designer design-revieweragainst the committed config and paste the updated (green) output into the PR body, confirming the@claude-plugins-officialslugs resolve correctly.Issue 2 — CLAUDE.md contradicts agents.json on host-vs-container mode for boss / dev / reviewer
config/agents.jsonadds"container": { "enabled": true, … }for boss, dev, and reviewer. Yet the new CLAUDE.md section (line ~67 of the final file) says:These three agents are now container-mode by the config change — they are not "running in host mode today" anymore. The doc is stale (or the config change is accidental). Either way, an operator reading CLAUDE.md will be confused about the actual deployment state.
If the container migration for boss/dev/reviewer is intentional (which it appears to be — they all have
credentials_host_dirandpluginsset), update CLAUDE.md to drop the "host mode today" caveat and add them to the operator'sjust containers-rebuildstep. If it's accidental, revert those threecontainer.enabledadditions from agents.json.Minor — unrelated change bundled in
bossmodelclaude-opus-4-7→claude-opus-4-7[1m]is unrelated to the plugin feature and arrives without discussion in the PR body (only in a commit message as "stopgap"). Not blocking, but worth a one-liner in the PR body so reviewers don't miss it.The core plugin mechanism is good. Once issue 1 is confirmed (re-verified smoke run) and issue 2 is resolved (doc or config aligned), this is ready to merge.
@ -38,2 +72,4 @@with a `plugins` key in their agents.json entry.## Label routingThis paragraph says boss/dev/reviewer "run in host mode today" but
config/agents.jsonin this very PR enables container mode for all three. One of the two needs updating to match the other.@ -41,2 +48,3 @@"penpot_mcp": true,"container": { "enabled": true }"container": { "enabled": true, "credentials_host_dir": "/home/charles/.config/claude-hooks/agent-env/designer" },"plugins": ["frontend-design@claude-plugins-official", "security-guidance@claude-plugins-official"]All plugin slugs here use
@claude-plugins-official, but the live-verification output in the PR body shows@claude-code-plugins. The marketplace registered inagent-plugins-installisanthropics/claude-plugins-official. Please re-run the smoke probe against this exact config and paste the updated output to confirm these slugs resolve correctly before merging.Review — PR #75: per-agent Claude Code plugin set via per-agent bind dirs
CI: ✅ green (run #1605,
4d03508, 2m43s)Live testing: 28/28 smoke probes green, end-to-end dispatch (#81/#82/#83) clean.
Issue #72 acceptance criteria
This PR is scoped as "first slice — A+ variant," so I'm evaluating against the ACs deliverable without #48 (SQLite store) or #53 (dashboard):
plugins: [...]per type in agents.json with correct defaultssettings.jsoninjection per dispatchpluginscolumnThe deferred/blocked items are explicitly acknowledged in the PR body. The architectural choice (host-side install vs. image-side bake) avoids image rebuilds on plugin-set changes — a legitimate trade-off, and the live results validate it.
Issues
1.
justfile~line 171 —|| truesilently swallows marketplace add failuresIf this fails for a real reason (bad auth, network outage, disk full), the error is completely suppressed. The operator then gets a confusing failure on the subsequent
claude plugin installline ("unknown marketplace" or similar) with no hint thatmarketplace addwas the root cause.The
|| trueis needed because the CLI exits non-zero when the marketplace is already registered — but the fix should be selective, not a blanket suppress:Or at minimum, remove the
>/dev/nullso a failure shows its output before|| truesuppresses the exit code — operators can at least see something went wrong in the terminal scroll.2.
justfile~line 173 —tail -1truncates plugin install error messages (minor)set -euo pipefailwill still abort the script ifclaude plugin installexits non-zero (pipefail propagates the non-zero through the pipe). But the error output is trimmed to the last line — multi-line errors (stack traces, "plugin not found in marketplace X" with a list) lose context. Suggest replacing withtee /dev/stderr | tail -1or just omitting| tail -1for cleaner debugging when installs fail.This is minor; it doesn't cause silent failures, just truncated error context.
What's solid
agent-env-sync: correct idempotent credential fan-out;session-env/pre-creation reasoning is well-explained.containers-rebuildsession-env fix: the rw sub-bind over the ro parent is the right solution to the EROFS cold-start issue; Docker's bind-mount stacking behaves correctly here.agents.jsonper-type plugin assignments: match the issue spec's proposed defaults exactly.pluginsdeclared), failing loud with the exactjustfix command — good operator UX.[1m]: stopgap is appropriate and tracked for post-merge revert.Deferred ACs to track
The runtime
settings.jsoninjection (writing the enabled plugin list per dispatch into the agent'sCLAUDE_CONFIG_DIRbeforequery()) is the main remaining AC from the issue. Once #48's SQLite per-instance store lands,main.tsneeds to: read the resolved plugin list (instance override → type default), writesettings.jsonwithenabledPlugins, then spawn. The hook point isseedContainerClaudeJsonas noted in the issue.Overall: the first slice delivers exactly what it claims, CI is green, live-tested. Approving. Fix #1 (silent marketplace failure) and #2 (tail truncation) are worth landing as a follow-up commit before the next slice rather than blocking merge.
Silent failure risk: if
marketplace addfails for a real reason (auth, network, disk), the>/dev/null 2>&1 || trueswallows everything. The operator then sees a confusing failure on theplugin installline below with no hint that marketplace registration was the root cause.The
|| trueis needed to handle the "already registered" case (CLI exits non-zero), but it should be selective:At minimum, drop
>/dev/nullso the output is visible before the|| trueswallows the exit code.| tail -1trims multi-line error output.set -euo pipefailstill aborts the script on a non-zero exit fromclaude plugin install(pipefail propagates the non-zero through the pipe), so this isn't a silent-failure bug — but a failed install will only show its last output line, losing stack traces / context. Considertee /dev/stderr | tail -1or simply removing| tail -1for cleaner error diagnosis.