feat(agents): per-agent Claude Code plugin set via per-agent bind dirs #75

Merged
code-lead merged 4 commits from feat/per-agent-plugins into main 2026-04-19 10:11:22 +00:00
Collaborator

Refs #72 (first slice — A+ variant per discussion).

Summary

Gives each container-mode agent its own Claude Code plugin set.
Also migrates boss, dev, and reviewer to container mode in
the 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 bind
read-only into each container at CLAUDE_CONFIG_DIR. A separate
writable sub-bind at session-env/ keeps Claude Code v2.1.112's
session state writes out of the ro parent. No Dockerfile changes,
no image rebuild on plugin-set changes — one just agent-plugins-install
on the host and the agent picks it up on its next dispatch.

What's in the diff

  • config/agents.json
    • Per-agent container.credentials_host_dir~/.config/claude-hooks/agent-env/<agent>/.
    • Per-agent plugins: [...] list for all five agents.
    • boss, dev, reviewer also gain container.enabled: true — moving them out of host mode (explicit scope expansion noted in §3 below).
    • Top-level container_image: "claude-hooks:dev" so containers-rebuild uses the local image instead of the never-published remote tag.
    • boss model bumped to claude-opus-4-7[1m] as a stopgap for the MCP tool-schema bloat until the forgejo-mcp trim (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.json stub from the shared master into each per-agent dir. Also pre-creates session-env/ subdir so Docker can bind-mount over it. Operator runs after every claude login.
    • agent-plugins-install [AGENT]claude plugin install per plugin declared in agents.json, into that agent's dir. Uses marketplace anthropics/claude-plugins-official.
    • containers-rebuild — adds a rw bind at session-env/ on top of the ro parent, so Claude Code v2.1.112 can write session state without tripping EROFS on 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 exact just command 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:

=== boss (claude-hooks-boss) — plugin probe ===
  [OK] security-guidance@claude-plugins-official
  [OK] typescript-lsp@claude-plugins-official
  [OK] claude-md-management@claude-plugins-official
=== design-reviewer (claude-hooks-design-reviewer) — plugin probe ===
  [OK] frontend-design@claude-plugins-official
  [OK] security-guidance@claude-plugins-official
=== designer (claude-hooks-designer) — plugin probe ===
  [OK] frontend-design@claude-plugins-official
  [OK] security-guidance@claude-plugins-official
=== dev (claude-hooks-dev) — plugin probe ===
  [OK] security-guidance@claude-plugins-official
  [OK] typescript-lsp@claude-plugins-official
=== reviewer (claude-hooks-reviewer) — plugin probe ===
  [OK] security-guidance@claude-plugins-official
  [OK] typescript-lsp@claude-plugins-official
  [OK] pr-review-toolkit@claude-plugins-official

Results: 28 passed, 0 failed

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. Committed config/agents.json has already been using the correct @claude-plugins-official slug; 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 matches agents.json in 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_DIR bind, 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 boss model → 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 pushing boss over 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 the forgejo-mcp trim (merged in PR #85) is rebuilt into the container image, after which boss can return to plain claude-opus-4-7. Tracked as a post-merge checklist item on this PR.

Operator flow

# After a credential refresh:
claude login
just agent-env-sync

# After editing plugins: in agents.json:
just agent-plugins-install

# After editing container.credentials_host_dir, image, or volume mounts:
just containers-rebuild

Test plan

  • just qa — green.
  • just agent-env-sync — syncs credentials + pre-creates session-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 + writable session-env sub-bind.
  • scripts/smoke-creds.sh — 28/28 green against committed config.
  • End-to-end dispatch (#81 / #82 / #83) exercised all 5 agents; 3 PRs merged on main.

Follow-ups

  • Post-merge: rebuild container image to pick up the forgejo-mcp tool trim (#81, merged in PR #85) and revert boss model to plain claude-opus-4-7. Tracked as a checklist item, not a new ticket.
  • #72 — SQLite + dashboard CRUD layer for per-instance customisation (not per-type).
  • Consider symlinking the marketplace clone across per-agent dirs to drop the 23 MB × N duplication — only worth doing if the pool grows or disk becomes tight.
Refs #72 (first slice — A+ variant per discussion). ## Summary Gives each container-mode agent its own Claude Code plugin set. Also **migrates `boss`, `dev`, and `reviewer` to container mode** in the 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 bind read-only into each container at `CLAUDE_CONFIG_DIR`. A separate writable sub-bind at `session-env/` keeps Claude Code v2.1.112's session state writes out of the ro parent. No Dockerfile changes, no image rebuild on plugin-set changes — one `just agent-plugins-install` on the host and the agent picks it up on its next dispatch. ## What's in the diff - **`config/agents.json`** - Per-agent `container.credentials_host_dir` → `~/.config/claude-hooks/agent-env/<agent>/`. - Per-agent `plugins: [...]` list for all five agents. - `boss`, `dev`, `reviewer` also gain `container.enabled: true` — moving them out of host mode (explicit scope expansion noted in §3 below). - Top-level `container_image: "claude-hooks:dev"` so `containers-rebuild` uses the local image instead of the never-published remote tag. - `boss` model bumped to `claude-opus-4-7[1m]` as a stopgap for the MCP tool-schema bloat until the `forgejo-mcp` trim (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.json` stub from the shared master into each per-agent dir. Also pre-creates `session-env/` subdir so Docker can bind-mount over it. Operator runs after every `claude login`. - `agent-plugins-install [AGENT]` — `claude plugin install` per plugin declared in `agents.json`, into that agent's dir. Uses marketplace `anthropics/claude-plugins-official`. - `containers-rebuild` — adds a rw bind at `session-env/` on top of the ro parent, so Claude Code v2.1.112 can write session state without tripping `EROFS` on 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 exact `just` command 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: ``` === boss (claude-hooks-boss) — plugin probe === [OK] security-guidance@claude-plugins-official [OK] typescript-lsp@claude-plugins-official [OK] claude-md-management@claude-plugins-official === design-reviewer (claude-hooks-design-reviewer) — plugin probe === [OK] frontend-design@claude-plugins-official [OK] security-guidance@claude-plugins-official === designer (claude-hooks-designer) — plugin probe === [OK] frontend-design@claude-plugins-official [OK] security-guidance@claude-plugins-official === dev (claude-hooks-dev) — plugin probe === [OK] security-guidance@claude-plugins-official [OK] typescript-lsp@claude-plugins-official === reviewer (claude-hooks-reviewer) — plugin probe === [OK] security-guidance@claude-plugins-official [OK] typescript-lsp@claude-plugins-official [OK] pr-review-toolkit@claude-plugins-official Results: 28 passed, 0 failed ``` 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. Committed `config/agents.json` has already been using the correct `@claude-plugins-official` slug; 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 matches `agents.json` in 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_DIR` bind, 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 `boss` model → `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 pushing `boss` over 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 the `forgejo-mcp` trim (merged in PR #85) is rebuilt into the container image, after which `boss` can return to plain `claude-opus-4-7`. Tracked as a post-merge checklist item on this PR. ## Operator flow ```text # After a credential refresh: claude login just agent-env-sync # After editing plugins: in agents.json: just agent-plugins-install # After editing container.credentials_host_dir, image, or volume mounts: just containers-rebuild ``` ## Test plan - [x] `just qa` — green. - [x] `just agent-env-sync` — syncs credentials + pre-creates `session-env/` for all 5 agents. - [x] `just agent-plugins-install` — installs each agent's plugin list. - [x] `just containers-rebuild` — all 5 containers pick up the per-agent bind dir + writable `session-env` sub-bind. - [x] `scripts/smoke-creds.sh` — 28/28 green against committed config. - [x] End-to-end dispatch (#81 / #82 / #83) exercised all 5 agents; 3 PRs merged on main. ## Follow-ups - **Post-merge**: rebuild container image to pick up the `forgejo-mcp` tool trim (#81, merged in PR #85) and revert `boss` model to plain `claude-opus-4-7`. Tracked as a checklist item, not a new ticket. - #72 — SQLite + dashboard CRUD layer for per-instance customisation (not per-type). - Consider symlinking the marketplace clone across per-agent dirs to drop the 23 MB × N duplication — only worth doing if the pool grows or disk becomes tight.
feat(agents): per-agent Claude Code plugin set via per-agent bind dirs
All checks were successful
qa / qa (pull_request) Successful in 2m32s
qa / dockerfile (pull_request) Successful in 9s
c049f898fb
First cut of milestone-16 plugin customization, A+ variant: plugins
live on the host in a per-agent bind-mount source dir (one per
container-mode agent), installed by the operator via a justfile
recipe, and bound read-only into each container at CLAUDE_CONFIG_DIR.

Gives per-agent tool surfaces today — `designer` gets
`frontend-design@claude-code-plugins` and `security-guidance@…`,
while other agents inherit nothing — without the image rebuild that
strategy B (bake into Dockerfile) would require.

Shape of the change:

- `config/agents.json`: per-agent `container.credentials_host_dir`
  pointing at `~/.config/claude-hooks/agent-env/<agent>/`, and a
  `plugins: [...]` list declaring the desired enable-set per agent.
  Also adds `container_image: "claude-hooks:dev"` at top-level so
  `just containers-rebuild` works against the local image instead of
  trying to pull the never-published remote tag.
- Only `designer` and `design-reviewer` participate in v1 — the
  other three agents still run in host mode and will join when they
  migrate to container mode (tracked on #72's SQLite / dashboard
  layer). Today they keep their existing host-mode behaviour, no
  plugins.
- `justfile` — two new recipes:
  - `agent-env-sync [AGENT]` — idempotent, copies `.credentials.json`
    + `.claude.json` stub from the shared master into each per-agent
    dir. Re-run after every `claude login`. Optional agent-name arg
    to target a single agent.
  - `agent-plugins-install [AGENT]` — runs
    `CLAUDE_CONFIG_DIR=<per-agent-dir> claude plugin install <slug>`
    for every plugin in that agent's `plugins:` list. Idempotent;
    re-running pulls the latest version. The official marketplace
    (`anthropics/claude-code`) is added silently if not already
    declared.
- `scripts/smoke-creds.sh` — extended with a plugin-presence probe
  that asserts every declared plugin appears in `claude plugin list`
  inside each container. Catches the "operator forgot to run
  agent-plugins-install" regression class.
- `CLAUDE.md` — new "Per-agent Claude Code plugins" section with
  the operator flow.

Verified live against the deployed setup: 14/14 smoke probes green
(credentials inode + Penpot MCP + canvas tools + plugins for both
designer and design-reviewer).

Storage cost per agent: ~23 MB (mostly the marketplace git clone).
5-agent pool would be ~115 MB; today's 2 agents = 48 MB. Acceptable.

Rescopes #72 — the Dockerfile-bake-in step is no longer the plan.
That ticket's remaining value is the SQLite + dashboard layer that
lets the enable-set be changed per instance (not per agent type)
without touching `agents.json`. Leaving #72 open; will adjust its
description on the next pass.
review: migrate boss / dev / reviewer to container mode + fix marketplace
All checks were successful
qa / qa (pull_request) Successful in 2m31s
qa / dockerfile (pull_request) Successful in 9s
56876db6c4
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.
reviewer left a comment

CI is green . Three issues need resolution before merge.


1. CLAUDE.md directly contradicts agents.json — docs will be wrong on day 1

File: CLAUDE.md, new section "Per-agent Claude Code plugins", last paragraph:

Only container-mode agents participate. boss, dev, reviewer run in host mode today and don't pick up per-agent plugins — when they migrate to container mode they'll inherit the same mechanism with a plugins key in their agents.json entry.

But the same PR sets container: { "enabled": true, … } on boss, dev, and reviewer in config/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, and reviewer are 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/reviewer

File: config/agents.json (all three new plugins arrays), scripts/smoke-creds.sh

agents.json declares plugins using the suffix @claude-plugins-official, e.g.:

"plugins": ["security-guidance@claude-plugins-official", "typescript-lsp@claude-plugins-official"]

But the PR body's live smoke output (which you ran and verified as passing) shows:

[OK] frontend-design@claude-code-plugins
[OK] security-guidance@claude-code-plugins

The smoke script uses grep -qx (exact match), so security-guidance@claude-plugins-official will never match security-guidance@claude-code-plugins. Every plugin check for every agent will silently FAIL.

One of these is wrong:

  • If the CLI reports @claude-code-plugins, every @claude-plugins-official entry in agents.json is wrong.
  • If the CLI reports @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 list on the host and copy the exact suffix the CLI reports. Update all agents.json plugin entries (and/or the marketplace registration in agent-plugins-install) to match.


3. Silent scope expansion: boss/dev/reviewer migrated to container mode without verification

File: config/agents.json

The 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, and reviewer are also silently being moved from host-mode to container-mode (new container.enabled: true). This is a meaningful operational change:

  • Those containers need to exist before the next dispatch.
  • The live test report shows 14/14 probes for designer and design-reviewer only — no smoke coverage for the three newly-containerised agents.

Fix: Either:

  • Add smoke results for boss, dev, and reviewer containers (confirming they exist, credentials are synced, and plugins install cleanly), or
  • Move the boss/dev/reviewer container migration to a separate PR so this one stays focused on the designer/design-reviewer slice described in the PR body.

Also update the "What's in the diff" section of the PR description to explicitly call out the host→container migration for these three agents.

CI is green ✅. Three issues need resolution before merge. --- ## 1. `CLAUDE.md` directly contradicts `agents.json` — docs will be wrong on day 1 **File:** `CLAUDE.md`, new section "Per-agent Claude Code plugins", last paragraph: > **Only container-mode agents participate.** `boss`, `dev`, `reviewer` run in host mode today and don't pick up per-agent plugins — when they migrate to container mode they'll inherit the same mechanism with a `plugins` key in their agents.json entry. But the **same PR** sets `container: { "enabled": true, … }` on `boss`, `dev`, and `reviewer` in `config/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`, and `reviewer` are 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`/`reviewer` **File:** `config/agents.json` (all three new `plugins` arrays), `scripts/smoke-creds.sh` `agents.json` declares plugins using the suffix `@claude-plugins-official`, e.g.: ```json "plugins": ["security-guidance@claude-plugins-official", "typescript-lsp@claude-plugins-official"] ``` But the PR body's **live smoke output** (which you ran and verified as passing) shows: ``` [OK] frontend-design@claude-code-plugins [OK] security-guidance@claude-code-plugins ``` The smoke script uses `grep -qx` (exact match), so `security-guidance@claude-plugins-official` will never match `security-guidance@claude-code-plugins`. Every plugin check for every agent will silently FAIL. One of these is wrong: - If the CLI reports `@claude-code-plugins`, every `@claude-plugins-official` entry in `agents.json` is wrong. - If the CLI reports `@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 list` on the host and copy the exact suffix the CLI reports. Update all `agents.json` plugin entries (and/or the marketplace registration in `agent-plugins-install`) to match. --- ## 3. Silent scope expansion: `boss`/`dev`/`reviewer` migrated to container mode without verification **File:** `config/agents.json` The 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`, and `reviewer` are also silently being moved from host-mode to container-mode (new `container.enabled: true`). This is a meaningful operational change: - Those containers need to exist before the next dispatch. - The live test report shows 14/14 probes for **designer** and **design-reviewer** only — no smoke coverage for the three newly-containerised agents. **Fix:** Either: - Add smoke results for `boss`, `dev`, and `reviewer` containers (confirming they exist, credentials are synced, and plugins install cleanly), **or** - Move the boss/dev/reviewer container migration to a separate PR so this one stays focused on the designer/design-reviewer slice described in the PR body. Also 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 plugins
Agents with container mode enabled carry their own **per-agent Claude
Collaborator

This paragraph says boss, dev, reviewer "run in host mode today" — but the same PR enables container: { "enabled": true } for all three in agents.json. After merge these docs are immediately wrong. Update to say all five agent types are now container-mode.

This paragraph says `boss`, `dev`, `reviewer` "run in host mode today" — but the same PR enables `container: { "enabled": true }` for all three in `agents.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" },
Collaborator

Plugin suffix @claude-plugins-official doesn't match the live smoke output in the PR body, which shows @claude-code-plugins. The smoke script uses grep -qx (exact match), so this will fail silently. Verify with CLAUDE_CONFIG_DIR=~/.config/claude-hooks/agent-env/boss claude plugin list and use the exact suffix the CLI reports.

Plugin suffix `@claude-plugins-official` doesn't match the live smoke output in the PR body, which shows `@claude-code-plugins`. The smoke script uses `grep -qx` (exact match), so this will fail silently. Verify with `CLAUDE_CONFIG_DIR=~/.config/claude-hooks/agent-env/boss claude plugin list` and use the exact suffix the CLI reports.
fix(container): rw session-env bind + boss on opus 1m stopgap
All checks were successful
qa / qa (pull_request) Successful in 2m31s
qa / dockerfile (pull_request) Successful in 8s
76589c74f7
Claude Code v2.1.112 writes session state to `CLAUDE_CONFIG_DIR/session-env/`
on cold start. Our per-agent bind mounts `CLAUDE_CONFIG_DIR` read-only, so
the mkdir fails and every task dies during shell-init before running any
tool — observed on boss task `4bcb6d02` for #81, which exited with:

  EROFS: read-only file system, mkdir '/home/claude/.config/claude-code/session-env'

Fix: overlay a rw bind at `session-env/` on top of the ro parent. The host
subdir is pre-created by `agent-env-sync` because Docker's mkdirat can't
punch a mountpoint into the ro parent on container start.

Also temporarily bumps boss to `claude-opus-4-7[1m]` as a stopgap for the
MCP tool-schema bloat that was tipping Opus-200k over budget on turn 1.
The forgejo-mcp trim (#81, merged as PR #85) reduces 70 RPCs → 19 which
should let boss return to 200k once the container image is rebuilt; leave
`[1m]` in place until then to avoid regressing runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer left a comment

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:

[OK] frontend-design@claude-code-plugins
[OK] security-guidance@claude-code-plugins

But config/agents.json (every agent) lists slugs as @claude-plugins-official, e.g.:

"plugins": ["frontend-design@claude-plugins-official", "security-guidance@claude-plugins-official"]

And justfile (agent-plugins-install) registers the marketplace as anthropics/claude-plugins-official.

The PR body explicitly notes that anthropics/claude-code is "the DEMO marketplace… not what we want" and switches to anthropics/claude-plugins-official — but the smoke output was clearly produced before that switch. The committed @claude-plugins-official slugs have not been re-verified end-to-end. If the marketplace short-name the CLI derives differs (e.g. plugins-official vs claude-plugins-official), every claude plugin install will 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-reviewer against the committed config and paste the updated (green) output into the PR body, confirming the @claude-plugins-official slugs resolve correctly.


Issue 2 — CLAUDE.md contradicts agents.json on host-vs-container mode for boss / dev / reviewer

config/agents.json adds "container": { "enabled": true, … } for boss, dev, and reviewer. Yet the new CLAUDE.md section (line ~67 of the final file) says:

Only container-mode agents participate. boss, dev, reviewer run in host mode today and don't pick up per-agent plugins — when they migrate to container mode they'll inherit the same mechanism…

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_dir and plugins set), update CLAUDE.md to drop the "host mode today" caveat and add them to the operator's just containers-rebuild step. If it's accidental, revert those three container.enabled additions from agents.json.


Minor — unrelated change bundled in

boss model claude-opus-4-7claude-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.

## 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: ``` [OK] frontend-design@claude-code-plugins [OK] security-guidance@claude-code-plugins ``` But `config/agents.json` (every agent) lists slugs as `@claude-plugins-official`, e.g.: ```json "plugins": ["frontend-design@claude-plugins-official", "security-guidance@claude-plugins-official"] ``` And `justfile` (`agent-plugins-install`) registers the marketplace as `anthropics/claude-plugins-official`. The PR body explicitly notes that `anthropics/claude-code` is "the DEMO marketplace… not what we want" and switches to `anthropics/claude-plugins-official` — but the smoke output was clearly produced before that switch. The committed `@claude-plugins-official` slugs have not been re-verified end-to-end. If the marketplace short-name the CLI derives differs (e.g. `plugins-official` vs `claude-plugins-official`), every `claude plugin install` will 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-reviewer` against the committed config and paste the updated (green) output into the PR body, confirming the `@claude-plugins-official` slugs resolve correctly. --- ### Issue 2 — CLAUDE.md contradicts agents.json on host-vs-container mode for boss / dev / reviewer `config/agents.json` adds `"container": { "enabled": true, … }` for **boss**, **dev**, and **reviewer**. Yet the new CLAUDE.md section (line ~67 of the final file) says: > **Only container-mode agents participate.** `boss`, `dev`, `reviewer` run in host mode today and don't pick up per-agent plugins — when they migrate to container mode they'll inherit the same mechanism… 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_dir` and `plugins` set), update CLAUDE.md to drop the "host mode today" caveat and add them to the operator's `just containers-rebuild` step. If it's accidental, revert those three `container.enabled` additions from agents.json. --- ### Minor — unrelated change bundled in `boss` model `claude-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.
CLAUDE.md Outdated
@ -38,2 +72,4 @@
with a `plugins` key in their agents.json entry.
## Label routing
Collaborator

This paragraph says boss/dev/reviewer "run in host mode today" but config/agents.json in this very PR enables container mode for all three. One of the two needs updating to match the other.

This paragraph says boss/dev/reviewer "run in host mode today" but `config/agents.json` in 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"]
Collaborator

All plugin slugs here use @claude-plugins-official, but the live-verification output in the PR body shows @claude-code-plugins. The marketplace registered in agent-plugins-install is anthropics/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.

All plugin slugs here use `@claude-plugins-official`, but the live-verification output in the PR body shows `@claude-code-plugins`. The marketplace registered in `agent-plugins-install` is `anthropics/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.
docs(claude-md): reflect all-5-agent container mode + correct plugin slug
All checks were successful
qa / qa (pull_request) Successful in 2m31s
qa / dockerfile (pull_request) Successful in 9s
4d03508081
Addresses PR #75 review issue 1 (slug mismatch `@claude-code-plugins`
→ `@claude-plugins-official`) and issue 2 (CLAUDE.md stale — now all
five agents run in container mode per the agents.json config in this
same PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer approved these changes 2026-04-19 10:10:30 +00:00
reviewer left a comment

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):

AC Status
plugins: [...] per type in agents.json with correct defaults
Smoke probe: plugin presence per agent
CLAUDE.md plugin management section
Boss/dev/reviewer migrated to container mode
Runtime settings.json injection per dispatch deferred (needs #48 first)
SQLite per-instance plugins column blocked by #48
Dashboard multi-select blocked by #53
Image-side bake (Dockerfile) ➡️ architectural variant: host-side install chosen instead

The 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 — || true silently swallows marketplace add failures

CLAUDE_CONFIG_DIR="$dir" claude plugin marketplace add anthropics/claude-plugins-official >/dev/null 2>&1 || true

If 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 install line ("unknown marketplace" or similar) with no hint that marketplace add was the root cause.

The || true is needed because the CLI exits non-zero when the marketplace is already registered — but the fix should be selective, not a blanket suppress:

out=$(CLAUDE_CONFIG_DIR="$dir" claude plugin marketplace add anthropics/claude-plugins-official 2>&1) || {
    # "already registered" variants are fine; anything else is a real failure
    if ! echo "$out" | grep -qi "already\|exists"; then
        echo "  WARNING: marketplace add failed: $out" >&2
    fi
}

Or at minimum, remove the >/dev/null so a failure shows its output before || true suppresses the exit code — operators can at least see something went wrong in the terminal scroll.

2. justfile ~line 173 — tail -1 truncates plugin install error messages (minor)

CLAUDE_CONFIG_DIR="$dir" claude plugin install "$plugin" 2>&1 | tail -1

set -euo pipefail will still abort the script if claude plugin install exits 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 with tee /dev/stderr | tail -1 or just omitting | tail -1 for 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-rebuild session-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.json per-type plugin assignments: match the issue spec's proposed defaults exactly.
  • smoke-creds.sh plugin probe: filtering correctly (only agents with plugins declared), failing loud with the exact just fix command — good operator UX.
  • Boss model bump to [1m]: stopgap is appropriate and tracked for post-merge revert.
  • CLAUDE.md: accurate, operator flow is clear.

Deferred ACs to track

The runtime settings.json injection (writing the enabled plugin list per dispatch into the agent's CLAUDE_CONFIG_DIR before query()) is the main remaining AC from the issue. Once #48's SQLite per-instance store lands, main.ts needs to: read the resolved plugin list (instance override → type default), write settings.json with enabledPlugins, then spawn. The hook point is seedContainerClaudeJson as 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.

## 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): | AC | Status | |---|---| | `plugins: [...]` per type in agents.json with correct defaults | ✅ | | Smoke probe: plugin presence per agent | ✅ | | CLAUDE.md plugin management section | ✅ | | Boss/dev/reviewer migrated to container mode | ✅ | | Runtime `settings.json` injection per dispatch | ⏳ deferred (needs #48 first) | | SQLite per-instance `plugins` column | ⏳ blocked by #48 | | Dashboard multi-select | ⏳ blocked by #53 | | Image-side bake (Dockerfile) | ➡️ architectural variant: host-side install chosen instead | The 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 — `|| true` silently swallows marketplace add failures** ```bash CLAUDE_CONFIG_DIR="$dir" claude plugin marketplace add anthropics/claude-plugins-official >/dev/null 2>&1 || true ``` If 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 install` line ("unknown marketplace" or similar) with no hint that `marketplace add` was the root cause. The `|| true` is needed because the CLI exits non-zero when the marketplace is already registered — but the fix should be selective, not a blanket suppress: ```bash out=$(CLAUDE_CONFIG_DIR="$dir" claude plugin marketplace add anthropics/claude-plugins-official 2>&1) || { # "already registered" variants are fine; anything else is a real failure if ! echo "$out" | grep -qi "already\|exists"; then echo " WARNING: marketplace add failed: $out" >&2 fi } ``` Or at minimum, remove the `>/dev/null` so a failure shows its output before `|| true` suppresses the exit code — operators can at least see something went wrong in the terminal scroll. **2. `justfile` ~line 173 — `tail -1` truncates plugin install error messages (minor)** ```bash CLAUDE_CONFIG_DIR="$dir" claude plugin install "$plugin" 2>&1 | tail -1 ``` `set -euo pipefail` will still abort the script if `claude plugin install` exits 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 with `tee /dev/stderr | tail -1` or just omitting `| tail -1` for 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-rebuild` session-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.json` per-type plugin assignments**: match the issue spec's proposed defaults exactly. - **smoke-creds.sh plugin probe**: filtering correctly (only agents with `plugins` declared), failing loud with the exact `just` fix command — good operator UX. - **Boss model bump to `[1m]`**: stopgap is appropriate and tracked for post-merge revert. - **CLAUDE.md**: accurate, operator flow is clear. ### Deferred ACs to track The runtime `settings.json` injection (writing the enabled plugin list per dispatch into the agent's `CLAUDE_CONFIG_DIR` before `query()`) is the main remaining AC from the issue. Once #48's SQLite per-instance store lands, `main.ts` needs to: read the resolved plugin list (instance override → type default), write `settings.json` with `enabledPlugins`, then spawn. The hook point is `seedContainerClaudeJson` as 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.
Collaborator

Silent failure risk: if marketplace add fails for a real reason (auth, network, disk), the >/dev/null 2>&1 || true swallows everything. The operator then sees a confusing failure on the plugin install line below with no hint that marketplace registration was the root cause.

The || true is needed to handle the "already registered" case (CLI exits non-zero), but it should be selective:

out=$(CLAUDE_CONFIG_DIR="$dir" claude plugin marketplace add anthropics/claude-plugins-official 2>&1) || {
    if ! echo "$out" | grep -qi "already\|exists"; then
        echo "  WARNING: marketplace add failed: $out" >&2
    fi
}

At minimum, drop >/dev/null so the output is visible before the || true swallows the exit code.

Silent failure risk: if `marketplace add` fails for a real reason (auth, network, disk), the `>/dev/null 2>&1 || true` swallows everything. The operator then sees a confusing failure on the `plugin install` line below with no hint that marketplace registration was the root cause. The `|| true` is needed to handle the "already registered" case (CLI exits non-zero), but it should be selective: ```bash out=$(CLAUDE_CONFIG_DIR="$dir" claude plugin marketplace add anthropics/claude-plugins-official 2>&1) || { if ! echo "$out" | grep -qi "already\|exists"; then echo " WARNING: marketplace add failed: $out" >&2 fi } ``` At minimum, drop `>/dev/null` so the output is visible before the `|| true` swallows the exit code.
Collaborator

| tail -1 trims multi-line error output. set -euo pipefail still aborts the script on a non-zero exit from claude 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. Consider tee /dev/stderr | tail -1 or simply removing | tail -1 for cleaner error diagnosis.

`| tail -1` trims multi-line error output. `set -euo pipefail` still aborts the script on a non-zero exit from `claude 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. Consider `tee /dev/stderr | tail -1` or simply removing `| tail -1` for cleaner error diagnosis.
code-lead deleted branch feat/per-agent-plugins 2026-04-19 10:11:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!75
No description provided.