The plugin shipped in CB-527 is invisible and has drifted — and it cannot deliver worker skills #362

Closed
opened 2026-09-05 07:37:06 +02:00 by ltms · 1 comment
Owner

What happened

An operator asked how to make any project fleet-compatible: package the setup as a Claude Code plugin with the MCP mount and a setup skill, install it globally, and have one skill onboard a project.

I planned it from scratch. It already exists. ef1e014 (CB-527) shipped exactly that, and 2e138a1 (CB-634) touched it last:

.claude-plugin/marketplace.json    marketplace "claude-bridge"  ->  ./plugin
plugin/.claude-plugin/plugin.json  plugin "claude-bridge" v0.1.0
plugin/.mcp.json                   mounts the daemon
plugin/skills/setup/SKILL.md       a full onboarding skill
plugin/README.md

The setup skill is good. It does preflight, merges rather than clobbers .mcp.json, pre-allows only read-only verbs, handles credentials by env-var name, and insists on a real spawn to verify because a green /healthz proves nothing.

Why nobody knew

grep -rn "plugin/" CLAUDE.md docs/*.md returns nothing. No instruction file mentions it. That is the root cause of everything below: a shipped capability nothing points at gets no maintenance, and the next session rebuilds it. This is precisely the failure the "Features" row in CLAUDE.md was added to prevent — applied to the plugin itself.

The drift

# Finding Evidence
1 Nothing references it grep -rn "plugin/" CLAUDE.md docs/*.md → empty
2 Mount-name collision plugin/.mcp.json mounts fleetd; PeerLauncher.java:34 is MCP_MOUNT_NAME = "fleet". A lead with both gets two mounts of one daemon and duplicate fleet_* tools
3 URL hardcoded http://127.0.0.1:8765/mcp one plugin cannot serve two hosts or ports
4 Stale identity advice setup §5 says pin primary.terminal:; CB-579 replaced that with fleet.leaders.*.tab. record Primary still exists (FleetConfig.java:954) so it is not dead, but it is no longer the mechanism
5 Stale install path README says ltms/claude-bridge; the repo is fleet/fleetd since CB-623
6 Stale names plugin and marketplace are both claude-bridge; the project renamed in CB-634

The finding that changes the design

The obvious next step looks like "move .claude/skills/* and .claude/agents/* into the plugin, so a worker in any repo can load implementer". Both halves of that are wrong.

Agents cannot move. ClaudeCodeLauncher.java:371 calls agentDefinitionFile(spec.cwd(), spec.role(), ".claude", "agents"); HerdrPeerLauncher.java:359-365 returns a path only when <cwd>/.claude/agents/<role>.md is a regular file; ClaudeCodeLauncher.java:391-393 adds --agent only when that is non-null. OpenCodeLauncher mirrors it for .opencode/agent. The file must be in the member's worktree. Move them and every member silently loses --agent.

The plugin does not reach members at all. ClaudeCodeLauncher.java:285 exports CLAUDE_CONFIG_DIR from the profile's configDir. Measured on the live Mac fleet:

  • all four Claude profiles set configDir (grep -c configDir fleetd/fleetd.yaml = 4);
  • each ccs instance has its own plugins/ directory — gx10 8 entries, ltms 11, ollama 9, work 9;
  • those are four separate real directories with four separate inodes, and each installed_plugins.json is a separate inode with an identical md5 (51c6e1c853e32e656b817e123fbbfcc5).

They are copies made once at adoption, not links. A user-scope install lands in exactly one instance's store and the others drift. The plugin is not a delivery mechanism for member-facing assets.

Proposed split — by audience, not by mechanism

Audience Delivered by Carries
operator / lead opening any project the plugin the MCP mount, setup, the bridge charter
member in a provisioned worktree worktree provisioning .claude/skills/*, .claude/agents/*

The second row already exists for agents — that is why agentDefinitionFile looks in the worktree. Extending provisioning to seed <worktree>/.claude/skills/ from a fleetd-owned source follows the grain of the design, and it is the thing that actually fixes "a worker in kb has no implementer skill". Every brief this fleet sends starts with Load the <name> skill., and outside this one repo that line is a no-op today.

Scope

  1. Make it visible — one CLAUDE.md addendum line, one Features entry. Minutes of work, and the item that stops this recurring.
  2. Fix the drift — mount name fleet, ${FLEETD_MCP_URL} with the 8765 default documented, names, README, the stale primary.terminal advice. Note this is a breaking change for a 0.1.0 install: mcp__fleetd__* becomes mcp__fleet__*.
  3. Seed .claude/skills/ into provisioned worktrees. Open: copy or symlink, and where the source lives. A symlink is one source of truth but breaks an archived tree; a copy is self-contained but drifts.

Full plan with the evidence: plans/fleet-plugin/plan.md.

Related: #359 (a second lead stalls coordination until one is named after coordinator.selfId — must land before anything that encourages two leads), #361 (coordination visibility).

## What happened An operator asked how to make any project fleet-compatible: package the setup as a Claude Code plugin with the MCP mount and a setup skill, install it globally, and have one skill onboard a project. I planned it from scratch. **It already exists.** `ef1e014` (CB-527) shipped exactly that, and `2e138a1` (CB-634) touched it last: ``` .claude-plugin/marketplace.json marketplace "claude-bridge" -> ./plugin plugin/.claude-plugin/plugin.json plugin "claude-bridge" v0.1.0 plugin/.mcp.json mounts the daemon plugin/skills/setup/SKILL.md a full onboarding skill plugin/README.md ``` The `setup` skill is good. It does preflight, merges rather than clobbers `.mcp.json`, pre-allows only read-only verbs, handles credentials by env-var name, and insists on a real spawn to verify because a green `/healthz` proves nothing. ## Why nobody knew `grep -rn "plugin/" CLAUDE.md docs/*.md` returns **nothing**. No instruction file mentions it. That is the root cause of everything below: a shipped capability nothing points at gets no maintenance, and the next session rebuilds it. This is precisely the failure the "Features" row in `CLAUDE.md` was added to prevent — applied to the plugin itself. ## The drift | # | Finding | Evidence | |---|---|---| | 1 | Nothing references it | `grep -rn "plugin/" CLAUDE.md docs/*.md` → empty | | 2 | Mount-name collision | `plugin/.mcp.json` mounts `fleetd`; `PeerLauncher.java:34` is `MCP_MOUNT_NAME = "fleet"`. A lead with both gets two mounts of one daemon and duplicate `fleet_*` tools | | 3 | URL hardcoded `http://127.0.0.1:8765/mcp` | one plugin cannot serve two hosts or ports | | 4 | Stale identity advice | `setup` §5 says pin `primary.terminal:`; CB-579 replaced that with `fleet.leaders.*.tab`. `record Primary` still exists (`FleetConfig.java:954`) so it is not dead, but it is no longer the mechanism | | 5 | Stale install path | README says `ltms/claude-bridge`; the repo is `fleet/fleetd` since CB-623 | | 6 | Stale names | plugin and marketplace are both `claude-bridge`; the project renamed in CB-634 | ## The finding that changes the design The obvious next step looks like "move `.claude/skills/*` and `.claude/agents/*` into the plugin, so a worker in any repo can load `implementer`". **Both halves of that are wrong.** **Agents cannot move.** `ClaudeCodeLauncher.java:371` calls `agentDefinitionFile(spec.cwd(), spec.role(), ".claude", "agents")`; `HerdrPeerLauncher.java:359-365` returns a path only when `<cwd>/.claude/agents/<role>.md` is a regular file; `ClaudeCodeLauncher.java:391-393` adds `--agent` only when that is non-null. `OpenCodeLauncher` mirrors it for `.opencode/agent`. The file must be in the **member's worktree**. Move them and every member silently loses `--agent`. **The plugin does not reach members at all.** `ClaudeCodeLauncher.java:285` exports `CLAUDE_CONFIG_DIR` from the profile's `configDir`. Measured on the live Mac fleet: - all four Claude profiles set `configDir` (`grep -c configDir fleetd/fleetd.yaml` = 4); - each `ccs` instance has its own `plugins/` directory — `gx10` 8 entries, `ltms` 11, `ollama` 9, `work` 9; - those are four separate real directories with four separate inodes, and each `installed_plugins.json` is a **separate inode with an identical md5** (`51c6e1c853e32e656b817e123fbbfcc5`). They are copies made once at adoption, not links. A user-scope install lands in exactly one instance's store and the others drift. **The plugin is not a delivery mechanism for member-facing assets.** ## Proposed split — by audience, not by mechanism | Audience | Delivered by | Carries | |---|---|---| | operator / lead opening any project | **the plugin** | the MCP mount, `setup`, the bridge charter | | member in a provisioned worktree | **worktree provisioning** | `.claude/skills/*`, `.claude/agents/*` | The second row already exists for agents — that is why `agentDefinitionFile` looks in the worktree. Extending provisioning to seed `<worktree>/.claude/skills/` from a fleetd-owned source follows the grain of the design, and it is the thing that actually fixes "a worker in *kb* has no `implementer` skill". Every brief this fleet sends starts with `Load the <name> skill.`, and outside this one repo that line is a no-op today. ## Scope 1. **Make it visible** — one `CLAUDE.md` addendum line, one Features entry. Minutes of work, and the item that stops this recurring. 2. **Fix the drift** — mount name `fleet`, `${FLEETD_MCP_URL}` with the 8765 default documented, names, README, the stale `primary.terminal` advice. Note this is a breaking change for a 0.1.0 install: `mcp__fleetd__*` becomes `mcp__fleet__*`. 3. **Seed `.claude/skills/` into provisioned worktrees.** Open: copy or symlink, and where the source lives. A symlink is one source of truth but breaks an archived tree; a copy is self-contained but drifts. Full plan with the evidence: `plans/fleet-plugin/plan.md`. Related: #359 (a second lead stalls coordination until one is named after `coordinator.selfId` — must land before anything that encourages two leads), #361 (coordination visibility).
Author
Owner

Correction to finding 1. I wrote "Nothing references it", with grep -rn "plugin/" CLAUDE.md docs/*.md as the evidence. The grep was accurate. The conclusion was not — I enumerated two channels and concluded about all of them.

The wiki documents it properly. wiki/11-Features.md has a full entry — Onboard a project with the plugin — with What / On / Why / Gotcha, and the Gotcha even explains why the plugin root is plugin/ and not the repo root. It is a good entry. It was written when the feature shipped, exactly as the CLAUDE.md Features rule requires.

So the real finding is sharper and more useful than the one I filed:

The Features entry existed, was correct, and still did not stop a session rebuilding the feature from scratch — because wiki/ is a submodule whose pointer is never advanced, so no session reads it by default.

That makes this a documentation-channel defect, not a missing-documentation defect. Writing the entry in the wiki is necessary and not sufficient. A capability an agent must not re-derive needs a line in CLAUDE.md, which is the only file every session loads. The Features rule in CLAUDE.md should probably say that.

Everything else in the description stands — findings 2 through 6 are drift the wiki entry did not prevent either, since it described the 0.1.0 names and was never updated when MCP_MOUNT_NAME became fleet.

Done so far:

  • CLAUDE.md addendum now names plugin/ and both structural limits.
  • wiki/11-Features.md entry updated for the rename, the ${FLEETD_MCP_URL} change, the lead-side-only scope, and a note that the entry alone was not enough.
  • Plugin renamed claude-bridge → fleet@fleetd, mount name fleetd → fleet, URL now ${FLEETD_MCP_URL}, README install path fixed, and the setup skill's stale primary.terminal: advice replaced with the fleet.leaders.*.tab mechanism. claude plugin validate ./plugin passes clean.

Warning for anyone reading claude plugin validate as proof: it does not read .mcp.json. I replaced the file with { this is not json at all and validation still passed with exit 0. It validates the manifest only. Do not use it to check a mount.

Scope item 3 (seeding .claude/skills/ into provisioned worktrees) is being implemented separately.

**Correction to finding 1.** I wrote "Nothing references it", with `grep -rn "plugin/" CLAUDE.md docs/*.md` as the evidence. The grep was accurate. The conclusion was not — I enumerated two channels and concluded about all of them. **The wiki documents it properly.** `wiki/11-Features.md` has a full entry — *Onboard a project with the plugin* — with What / On / Why / Gotcha, and the Gotcha even explains why the plugin root is `plugin/` and not the repo root. It is a good entry. It was written when the feature shipped, exactly as the `CLAUDE.md` Features rule requires. So the real finding is sharper and more useful than the one I filed: > **The Features entry existed, was correct, and still did not stop a session rebuilding the feature from scratch** — because `wiki/` is a submodule whose pointer is never advanced, so no session reads it by default. That makes this a documentation-*channel* defect, not a missing-documentation defect. Writing the entry in the wiki is necessary and not sufficient. A capability an agent must not re-derive needs a line in `CLAUDE.md`, which is the only file every session loads. The Features rule in `CLAUDE.md` should probably say that. Everything else in the description stands — findings 2 through 6 are drift the wiki entry did not prevent either, since it described the 0.1.0 names and was never updated when `MCP_MOUNT_NAME` became `fleet`. **Done so far:** - `CLAUDE.md` addendum now names `plugin/` and both structural limits. - `wiki/11-Features.md` entry updated for the rename, the `${FLEETD_MCP_URL}` change, the lead-side-only scope, and a note that the entry alone was not enough. - Plugin renamed `claude-bridge` → `fleet@fleetd`, mount name `fleetd` → `fleet`, URL now `${FLEETD_MCP_URL}`, README install path fixed, and the `setup` skill's stale `primary.terminal:` advice replaced with the `fleet.leaders.*.tab` mechanism. `claude plugin validate ./plugin` passes clean. **Warning for anyone reading `claude plugin validate` as proof:** it does **not** read `.mcp.json`. I replaced the file with `{ this is not json at all` and validation still passed with exit 0. It validates the manifest only. Do not use it to check a mount. Scope item 3 (seeding `.claude/skills/` into provisioned worktrees) is being implemented separately.
ltms closed this issue 2026-09-05 08:34:39 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#362