CB-616: a configured role charter makes every claude-code member unspawnable — the charter travels through argv #119

Closed
opened 2026-08-22 08:59:08 +02:00 by ltms · 1 comment
Owner

Found on 2026-08-22 while spawning two architects for a design task. One spawned, one failed.

What happened

POST /members {"role":"architect","profile":"sol"}    -> 200, member READY
POST /members {"role":"architect","profile":"opus"}   -> 500 "Server Error"

The daemon log carries the real reason, which the HTTP response does not:

dev.ltms.bridged.herdr.HerdrException: herdr error [invalid_agent_argument]:
  agent arguments cannot be encoded safely for the target shell

fleet.charters.architect is configured and is 1742 bytes of multi-line prose
(charterSha256=3b7c0c73…, confirmed on the member that did start).

Root cause — the two launchers carry the charter differently

Launcher How the charter travels Result
ClaudeCodeLauncher an argv flag: --append-system-prompt <charter> (ClaudeCodeLauncher.java:285) herdr must shell-encode 1742 bytes with newlines and quotes, and refuses
OpenCodeLauncher written to a file, pointed at by OPENCODE_CONFIG no shell encoding, works

The composition itself is fine and shared — HerdrPeerLauncher.spawnInternal builds
roleCharter + "\n\n" + REPLY_CHARTER for both. Only the delivery differs, and only one of the two
delivery paths can carry real prose.

Blast radius — bigger than the architect role

Live now. Every architect on a claude-code profile (opus, sonnet, local, local-direct)
cannot spawn at all. Only opencode architects work. That disables the two-architect design flow for
every Claude model, which is the flow CB-548 exists to provide.

Latent, and worse. dev and reviewer escape today only because no charter is configured for
them. fleet.charters is a HOT key, so the moment an operator fills in fleet.charters.dev, every
claude-code dev breaks at the very next spawn, with no restart and no warning. The config that
causes it looks completely reasonable and the example file actively suggests it — the commented
block in bridged.example.yaml shows dev and reviewer charter text ready to uncomment.

The failure is badly reported. The caller gets a bare 500 Server Error. The cause is only in
the daemon log. An operator who does not tail the log sees a profile that "just does not work".

Fix directions

Not yet designed — listing what the fix has to choose between.

  1. Match the opencode shape: give the charter a file. The natural fix, and it is the one that
    already works. Needs checking whether Claude Code can take a system prompt from a file rather
    than inline; if it cannot, this direction dies and 2 or 3 is the answer. Unverified.
  2. Pass it through the pane env and have the launch read it back. Note the CB-596 round-2
    finding first: the pane env overlay is applied at pane creation and is then overwritten by the
    login shell for any name secrets.sh exports. A fresh name it does not export would survive.
  3. A herdr-side argv channel. Same seam CB-596 already hit: AgentControl.start takes a fixed
    kind plus trailing CLI args, not an arbitrary argv or env map. Likely needs a herdr change.
  4. Refuse it at config load, whatever else we do. A charter that cannot be delivered to a
    configured profile should fail the reload with a clear message naming the profile and the role —
    not spawn fine for one backend and 500 for another hours later. This is worth doing even if 1
    works, because it is the difference between a loud config error and a mystery.

Acceptance

  • A claude-code architect spawns with the configured multi-line charter, and its reported
    charterSha256 equals the one an opencode architect reports for the same charter. Same bytes to
    both backends, or the two peers are not working from the same contract.
  • Setting fleet.charters.dev to multi-line text does not break a sonnet dev spawn.
  • The 500 is replaced by a response that names the cause.
  • The test spawns through the real launcher path. A test that calls the encoder or the launcher
    seam directly walks around the gate this bug lives in — see #113 for the three times that has
    already fooled us.

Workaround until then

Run architects on an opencode profile. sol works; opus and sonnet do not.

Found on 2026-08-22 while spawning two architects for a design task. One spawned, one failed. ## What happened ``` POST /members {"role":"architect","profile":"sol"} -> 200, member READY POST /members {"role":"architect","profile":"opus"} -> 500 "Server Error" ``` The daemon log carries the real reason, which the HTTP response does not: ``` dev.ltms.bridged.herdr.HerdrException: herdr error [invalid_agent_argument]: agent arguments cannot be encoded safely for the target shell ``` `fleet.charters.architect` is configured and is 1742 bytes of multi-line prose (`charterSha256=3b7c0c73…`, confirmed on the member that did start). ## Root cause — the two launchers carry the charter differently | Launcher | How the charter travels | Result | |---|---|---| | `ClaudeCodeLauncher` | an **argv** flag: `--append-system-prompt <charter>` (`ClaudeCodeLauncher.java:285`) | herdr must shell-encode 1742 bytes with newlines and quotes, and refuses | | `OpenCodeLauncher` | written to a **file**, pointed at by `OPENCODE_CONFIG` | no shell encoding, works | The composition itself is fine and shared — `HerdrPeerLauncher.spawnInternal` builds `roleCharter + "\n\n" + REPLY_CHARTER` for both. Only the delivery differs, and only one of the two delivery paths can carry real prose. ## Blast radius — bigger than the architect role **Live now.** Every architect on a claude-code profile (`opus`, `sonnet`, `local`, `local-direct`) cannot spawn at all. Only opencode architects work. That disables the two-architect design flow for every Claude model, which is the flow CB-548 exists to provide. **Latent, and worse.** `dev` and `reviewer` escape today only because no charter is configured for them. `fleet.charters` is a HOT key, so the moment an operator fills in `fleet.charters.dev`, every claude-code dev breaks at the very next spawn, with no restart and no warning. The config that causes it looks completely reasonable and the example file actively suggests it — the commented block in `bridged.example.yaml` shows dev and reviewer charter text ready to uncomment. **The failure is badly reported.** The caller gets a bare 500 `Server Error`. The cause is only in the daemon log. An operator who does not tail the log sees a profile that "just does not work". ## Fix directions Not yet designed — listing what the fix has to choose between. 1. **Match the opencode shape: give the charter a file.** The natural fix, and it is the one that already works. Needs checking whether Claude Code can take a system prompt from a file rather than inline; if it cannot, this direction dies and 2 or 3 is the answer. **Unverified.** 2. **Pass it through the pane env** and have the launch read it back. Note the CB-596 round-2 finding first: the pane env overlay is applied at pane creation and is then overwritten by the login shell for any name `secrets.sh` exports. A fresh name it does not export would survive. 3. **A herdr-side argv channel.** Same seam CB-596 already hit: `AgentControl.start` takes a fixed `kind` plus trailing CLI args, not an arbitrary argv or env map. Likely needs a herdr change. 4. **Refuse it at config load, whatever else we do.** A charter that cannot be delivered to a configured profile should fail the reload with a clear message naming the profile and the role — not spawn fine for one backend and 500 for another hours later. This is worth doing even if 1 works, because it is the difference between a loud config error and a mystery. ## Acceptance - A claude-code architect spawns with the configured multi-line charter, and its reported `charterSha256` equals the one an opencode architect reports for the same charter. Same bytes to both backends, or the two peers are not working from the same contract. - Setting `fleet.charters.dev` to multi-line text does not break a `sonnet` dev spawn. - The 500 is replaced by a response that names the cause. - The test spawns through the real launcher path. A test that calls the encoder or the launcher seam directly walks around the gate this bug lives in — see #113 for the three times that has already fooled us. ## Workaround until then Run architects on an opencode profile. `sol` works; `opus` and `sonnet` do not.
Author
Owner

Fixed and confirmed live on cc47672.

The original error is gone. Getting there took two more fixes on top of CB-617, both found only by running the real binary:

  1. Claude Code refuses to start when both --append-system-prompt and --append-system-prompt-file are on the command line: Error: Cannot use both --append-system-prompt and --append-system-prompt-file. Please use only one. CB-617 put the role charter on the file flag and left the reply charter inline, so the pane exited at launch and bridged reported it as spawn_timeout, which hides the real cause. Both charters now go in the one file, reply charter last.
  2. A Claude Code agent definition needs name: in its frontmatter. Ours had only description:, so --agent architect failed with not found. Available agents: claude, Explore, ....

Live check — the exact spawn that failed here:

POST /members {"role":"architect","profile":"opus","cwd":"/Users/dai.ha/LTMS/claude-bridge"}
-> state ready, bridge MCP mounted

and the member's own answer:

1) bridge_whoami reports: architect (slot "opus", sessionId term_659a04ec7579c4f).
2) My instructions say I am an architect — I refine scope, criteria, risks and unit splits.
3) I must never commit production code or open a pull request.

Lines 2 and 3 are the text of .claude/agents/architect.md, so the agent definition, the role charter and the reply charter all reach the member together. 874 tests pass.

Closing.

Fixed and confirmed live on `cc47672`. The original error is gone. Getting there took two more fixes on top of CB-617, both found only by running the real binary: 1. Claude Code refuses to start when both `--append-system-prompt` and `--append-system-prompt-file` are on the command line: `Error: Cannot use both --append-system-prompt and --append-system-prompt-file. Please use only one.` CB-617 put the role charter on the file flag and left the reply charter inline, so the pane exited at launch and `bridged` reported it as `spawn_timeout`, which hides the real cause. Both charters now go in the one file, reply charter last. 2. A Claude Code agent definition needs `name:` in its frontmatter. Ours had only `description:`, so `--agent architect` failed with `not found. Available agents: claude, Explore, ...`. Live check — the exact spawn that failed here: ``` POST /members {"role":"architect","profile":"opus","cwd":"/Users/dai.ha/LTMS/claude-bridge"} -> state ready, bridge MCP mounted ``` and the member's own answer: ``` 1) bridge_whoami reports: architect (slot "opus", sessionId term_659a04ec7579c4f). 2) My instructions say I am an architect — I refine scope, criteria, risks and unit splits. 3) I must never commit production code or open a pull request. ``` Lines 2 and 3 are the text of `.claude/agents/architect.md`, so the agent definition, the role charter and the reply charter all reach the member together. 874 tests pass. Closing.
ltms closed this issue 2026-08-22 12:36:05 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#119