From b3cba19c1e48ce066c0ec1443e69b97b464296b4 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Thu, 13 Aug 2026 15:26:34 +0200 Subject: [PATCH] CB-539/CB-542: document subscription:true profile opt-in and its env: contradiction --- 11-Features.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/11-Features.md b/11-Features.md index ea22cc9..dfa4cc2 100644 --- a/11-Features.md +++ b/11-Features.md @@ -30,6 +30,7 @@ six weeks, and the table alone will not carry it. | [Reply nudges follow the delegating lead](#reply-nudges-follow-the-delegating-lead) | automatic (retires `primary:`) | CB-532 | `mcp/PrimaryRegistry` | | [Weighted worker placement](#weighted-worker-placement) | `placement: weighted` + `weight` / `maxLoad` | CB-518 | `placement/` | | [Give workers a toolchain](#give-workers-a-toolchain) | per-profile `env:` | CB-511 | `worker/HerdrPeerLauncher` | +| [Run a worker on the subscription](#run-a-worker-on-the-subscription) | profile `subscription: true` | CB-539 | `worker/ClaudeCodeLauncher` | | [Isolated worktree per worker](#isolated-worktree-per-worker) | `bridge_spawn{worktree, ticket}` | CB-301-ext | `session/GitWorktrees` | | [Worker tool-surface isolation](#worker-tool-surface-isolation) | automatic | CB-525 | `session/GitWorktrees` | | [Worker opens its own PR](#worker-opens-its-own-pr) | `gitTokenEnv:` / `gitHostEnv:` | CB-302 | `worker/HerdrPeerLauncher` | @@ -109,6 +110,27 @@ onto one backend regardless of what it costs or how loaded it is. governs *unqualified* spawns. Equal-weight candidates tie-break on **YAML definition order**, so that order is load-bearing config, not cosmetics (CB-524). +## Run a worker on the subscription + +**What.** Lets a claude-code worker run on the operator's Claude subscription, on purpose, when no +off-subscription endpoint exists for its family (e.g. `sonnet` on `ccs`). The launcher injects +neither `ANTHROPIC_BASE_URL` nor `ANTHROPIC_AUTH_TOKEN` and skips `SubscriptionGuard`'s base_url +requirement *for that profile only* — every other profile keeps the hard refusal. + +**On.** Add `subscription: true` to a profile. The default (absent/`false`) keeps today's hard +boundary: a claude-code profile with no base_url may not spawn, because doing so would bill the +subscription. + +**Why.** Some model families (e.g. `sonnet` on `ccs`) have no off-subscription endpoint to point a +worker at. Rather than leave those profiles unspawnable, this is an explicit, visible opt-in — a +spawn under it logs a WARN naming the profile, so billing the subscription is never an accident. + +**Gotcha.** `subscription: true` contradicts a `baseUrl` (the two state opposite intents) and is +refused at spawn if both are set. The same contradiction is refused at config load for the `env:` +map: on the subscription path the guard is skipped and the adapter writes neither Anthropic key, so +an `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` in `env:` would reach the worker having passed no +guard at all (CB-542). A subscription profile may still carry `env:` — just not those two keys. + ## Give workers a toolchain **What.** Propagates the daemon's own `PATH` to every worker, plus a literal per-profile `env:` map. @@ -120,10 +142,13 @@ env map and herdr merges it into *its own* process env — so before this, a wor `PATH` the herdr server happened to be started with. On a long-lived herdr that can predate your toolchain entirely, leaving workers unable to run `mvn` or `java` at all. -**Gotcha.** Adapter-owned variables win over `env:` — the `ANTHROPIC_*`/`CLAUDE_*` wiring is applied -after it, so an `env:` entry cannot repoint a worker past the `SubscriptionGuard`. Since the default -is the *daemon's* `PATH`, start the daemon with a good one (see the `PATH` lines in -`deploy/dev.ltms.bridged.plist` and `deploy/bridged.service`). +**Gotcha.** On an off-subscription profile, adapter-owned variables win over `env:` — the +`ANTHROPIC_*`/`CLAUDE_*` wiring is applied after it, so an `env:` entry cannot repoint a worker past +the `SubscriptionGuard`. A `subscription: true` profile is the exception: it skips the guard and +injects neither key, so an `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` in its `env:` would survive +unguarded — that configuration is refused at load (see [Run a worker on the subscription](#run-a-worker-on-the-subscription)). +Since the default PATH is the *daemon's* own, start the daemon with a good one (see the +`PATH` lines in `deploy/dev.ltms.bridged.plist` and `deploy/bridged.service`). ## Isolated worktree per worker