The hunter skill has no MemberRole, so every sweep is spawned under a role whose agent file contradicts it #568

Open
opened 2026-09-12 12:40:27 +02:00 by ltms · 1 comment
Owner

Measured on main at ba2f4d1. I walked into this today and lost a worker turn to it, so this is a report of a real failure, not a hypothetical.

The mismatch

This repo ships three delegation skills and three member roles, and they do not line up:

skill .claude/skills/ matching MemberRole matching .claude/agents/*.md
implementer yes DEV dev.md
reviewer yes REVIEWER reviewer.md
hunter yes none none

MemberRole (fleetd/src/main/java/dev/ltms/fleet/peer/MemberRole.java:24) is a closed enum. ClaudeCodeLauncher passes --agent <role.wireName()> when <cwd>/.claude/agents/<role>.md exists. So there is no way to spawn a member whose agent file matches the hunter skill — the lead must pick one of the other two roles, and whichever they pick contradicts the skill.

What that actually does

I spawned fleet_spawn{role: "reviewer", profile: "terra", ...} and briefed it Load the hunter skill. with a sweep of 18 finally blocks, asking for a ranked report, a per-block table, an instrumented hit count, and a verdict.

I got back one finding in the reviewer's four-line form. The sweep was not delivered.

Both contracts were followed — just by different parts of the worker's instructions:

  • reviewer.md:19: "Report the single most important real issue in this form:" — the output cap won.
  • reviewer.md: "Do not run the build." — directly contradicts a hunt, which cannot confirm anything without running it. The worker ran it anyway, following my brief over its role file.

So the worker was resolving a conflict on every axis, and the lead has no way to know which side won on which. The hunter skill even anticipates half of this — .claude/skills/hunter/SKILL.md:3 says "Do NOT load reviewer for this; the two want different output" — but that warning is addressed to the worker, about which skill to load. Nothing warns the lead that the role parameter carries a second, conflicting contract.

CLAUDE.md's addendum has the same gap. It says reviewer and hunter "are not interchangeable" and that naming the wrong one hands the worker two contradictory output contracts — which is right, and is about the skill name in the brief. It never says the role argument to fleet_spawn does the same thing.

Why this is worth fixing rather than remembering

The failure is silent and it looks like a worker problem. The worker produced a well-formed, correct-looking finding. Nothing errors, nothing warns, and the missing 90% of the work is invisible unless the lead remembers what they asked for. This has now cost turns twice in two days by the same mechanism — the other instance was a brief that named reviewer for a multi-finding sweep.

Options, in the order I would consider them

  1. Add a HUNTER member role plus .claude/agents/hunter.md. The honest fix: one role per skill, and the agent file can then say "run the build, report several findings, change nothing" without fighting anything. Costs an enum value, a wire name, and a file that must be committed (a plugin cannot carry it — ClaudeCodeLauncher requires it under the member's own cwd).
  2. Refuse the combination at the seam. Have fleet_spawn reject, or at least warn on, a role whose agent file is known to conflict with the skill the brief names. Weak, because the daemon does not read the brief.
  3. Document the pairing in CLAUDE.md's skill list — one line: which role each skill must be spawned with. Cheapest, and worth doing regardless of 1. On its own it only helps a lead who re-reads that line.

I would do 1 and 3.

Current workaround, for anyone hunting before this is fixed

Spawn role: "dev", not role: "reviewer". dev.md permits running the build and imposes no finding cap. Then override its other half explicitly in the brief: do not commit, do not open a pull request, change nothing. Without that the worker will try to open a PR for a sweep that has no diff.

Related: #556 and #567 (both found by sweeps of this kind), and CLAUDE.md → Project addendum → the skill list, which needs the extra line either way.

Measured on `main` at `ba2f4d1`. I walked into this today and lost a worker turn to it, so this is a report of a real failure, not a hypothetical. ## The mismatch This repo ships **three** delegation skills and **three** member roles, and they do not line up: | skill | `.claude/skills/` | matching `MemberRole` | matching `.claude/agents/*.md` | |---|---|---|---| | `implementer` | yes | `DEV` | `dev.md` | | `reviewer` | yes | `REVIEWER` | `reviewer.md` | | `hunter` | yes | **none** | **none** | `MemberRole` (`fleetd/src/main/java/dev/ltms/fleet/peer/MemberRole.java:24`) is a closed enum. `ClaudeCodeLauncher` passes `--agent <role.wireName()>` when `<cwd>/.claude/agents/<role>.md` exists. So there is no way to spawn a member whose agent file matches the `hunter` skill — the lead must pick one of the other two roles, and whichever they pick contradicts the skill. ## What that actually does I spawned `fleet_spawn{role: "reviewer", profile: "terra", ...}` and briefed it `Load the hunter skill.` with a sweep of 18 `finally` blocks, asking for a ranked report, a per-block table, an instrumented hit count, and a verdict. I got back **one finding in the reviewer's four-line form**. The sweep was not delivered. Both contracts were followed — just by different parts of the worker's instructions: - `reviewer.md:19`: *"Report the single most important real issue in this form:"* — the output cap won. - `reviewer.md`: **"Do not run the build."** — directly contradicts a hunt, which cannot confirm anything without running it. The worker ran it anyway, following my brief over its role file. So the worker was resolving a conflict on every axis, and the lead has no way to know which side won on which. The `hunter` skill even anticipates half of this — `.claude/skills/hunter/SKILL.md:3` says *"Do NOT load `reviewer` for this; the two want different output"* — but that warning is addressed to the **worker**, about which skill to load. Nothing warns the **lead** that the `role` parameter carries a second, conflicting contract. `CLAUDE.md`'s addendum has the same gap. It says `reviewer` and `hunter` "are not interchangeable" and that naming the wrong one hands the worker two contradictory output contracts — which is right, and is about the **skill name in the brief**. It never says the `role` argument to `fleet_spawn` does the same thing. ## Why this is worth fixing rather than remembering The failure is silent and it looks like a worker problem. The worker produced a well-formed, correct-looking finding. Nothing errors, nothing warns, and the missing 90% of the work is invisible unless the lead remembers what they asked for. This has now cost turns twice in two days by the same mechanism — the other instance was a brief that named `reviewer` for a multi-finding sweep. ## Options, in the order I would consider them 1. **Add a `HUNTER` member role plus `.claude/agents/hunter.md`.** The honest fix: one role per skill, and the agent file can then say "run the build, report several findings, change nothing" without fighting anything. Costs an enum value, a wire name, and a file that must be committed (a plugin cannot carry it — `ClaudeCodeLauncher` requires it under the member's own cwd). 2. **Refuse the combination at the seam.** Have `fleet_spawn` reject, or at least warn on, a role whose agent file is known to conflict with the skill the brief names. Weak, because the daemon does not read the brief. 3. **Document the pairing in `CLAUDE.md`'s skill list** — one line: which `role` each skill must be spawned with. Cheapest, and worth doing regardless of 1. On its own it only helps a lead who re-reads that line. I would do 1 and 3. ## Current workaround, for anyone hunting before this is fixed Spawn `role: "dev"`, not `role: "reviewer"`. `dev.md` permits running the build and imposes no finding cap. Then override its other half explicitly in the brief: **do not commit, do not open a pull request, change nothing.** Without that the worker will try to open a PR for a sweep that has no diff. Related: #556 and #567 (both found by sweeps of this kind), and `CLAUDE.md` → *Project addendum* → the skill list, which needs the extra line either way.
Author
Owner

Correction to the #568 brief — read before you commit

A rule your brief did not carry. It reaches you here rather than as a message because a message
cannot reach a busy member.

Never put a command whose exit status you will report behind a pipe

cmd | tail reports tail's exit status, not the command's. A BUILD FAILURE vanishes behind
a zero exit and you report a clean run that never happened. Capture the status first:

mvn -q test > /tmp/build.log 2>&1; status=$?
tail -40 /tmp/build.log
echo "mvn exit: $status"

Relevant here because your criterion 4 is a suite total, and criterion 3 asks what every
MemberRole enumeration does with the new value — both are readings you report as fact.

This is trap #1 in this repo's own scripts/redeploy-fleetd.sh header, line 10. The lesson was
hard-coded into one script and never told to the members who run builds. That gap is #591.

Also, restated because they belong to the role rather than the task

  • You never merge. The lead does.
  • Stage files explicitly by path. Never git add -A — a provisioned worktree contains files
    that cannot be committed and will not tell you so.

Unchanged

The rest of the brief stands, including the hard boundary: edit only inside
## Project addendum — claude-bridge and below in CLAUDE.md, never the canonical block, and
say plainly that you could not run the wiki sync check.

Source: the fleet01 lead's dev charter, which carries these on every spawn. This host has no
dev charter, so its members were told none of them. Being fixed.

## Correction to the #568 brief — read before you commit A rule your brief did not carry. It reaches you here rather than as a message because a message cannot reach a busy member. ### Never put a command whose exit status you will report behind a pipe `cmd | tail` reports **tail's** exit status, not the command's. A `BUILD FAILURE` vanishes behind a zero exit and you report a clean run that never happened. Capture the status first: ```bash mvn -q test > /tmp/build.log 2>&1; status=$? tail -40 /tmp/build.log echo "mvn exit: $status" ``` Relevant here because your criterion 4 is a suite total, and criterion 3 asks what every `MemberRole` enumeration does with the new value — both are readings you report as fact. This is trap #1 in this repo's own `scripts/redeploy-fleetd.sh` header, line 10. The lesson was hard-coded into one script and never told to the members who run builds. That gap is #591. ### Also, restated because they belong to the role rather than the task - **You never merge.** The lead does. - **Stage files explicitly by path.** Never `git add -A` — a provisioned worktree contains files that cannot be committed and will not tell you so. ### Unchanged The rest of the brief stands, including the hard boundary: edit only inside `## Project addendum — claude-bridge` and below in `CLAUDE.md`, never the canonical block, and say plainly that you could not run the wiki sync check. *Source: the fleet01 lead's `dev` charter, which carries these on every spawn. This host has no `dev` charter, so its members were told none of them. Being fixed.*
agent referenced this issue from a commit 2026-09-19 10:09:15 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#568