feat(missions): a canary backend for testing a CLI version on the real path

Claude Code 2.1.223 -> 2.1.226 is worth taking (2.1.225 fixes a transient 401
that replaced a long-lived CLAUDE_CODE_OAUTH_TOKEN with a short-lived one and
broke HEADLESS sessions until restart — which for us means a failed phase). But
the image every mission uses is not the place to find out whether a new CLI
still delegates, still accepts `--settings`, and still finishes.

`canary-claude` is a real rootfs built from the candidate version, credentialed
identically to `claude`, so a mission can exercise it through the production
path: egress, stop gate, delegation, delivery, streaming. Testing a new CLI
against a different provider would not be testing the thing we are about to ship.

Named explicitly rather than matched on a prefix. An unrecognised backend must
still be refused at launch — that is what `backend_can_run_a_mission` and the
harness's `microvm-negctl` scenario assert — and loosening the credential map is
exactly how that guard gets softened by accident. A test pins both halves.

Already cleared by direct measurement in a booted 2.1.226 VM, before this:
  - `--settings` and `--agents` still exist
  - the workspace trust prompt added in 2.1.225 does NOT apply: `--help` states
    the dialog is skipped in non-interactive mode (`-p`, or stdout not a TTY).
    We use both.
This commit is contained in:
Omar Sobh
2026-08-07 23:37:04 -07:00
parent 8c93cd8569
commit 4193ae2cda
+35 -4
View File
@@ -170,10 +170,22 @@ struct Credential {
/// at is worse than not launching. /// at is worse than not launching.
fn microvm_credential_for(backend: Option<&str>) -> Result<Credential, String> { fn microvm_credential_for(backend: Option<&str>) -> Result<Credential, String> {
match backend { match backend {
None | Some("") | Some("default") | Some("claude") => Ok(Credential { // `canary-*` backends are a REAL rootfs built from a candidate CLI
source: "CLAUDE_CODE_OAUTH_TOKEN", // version, run through the production path — egress, stop gate,
target: "CLAUDE_CODE_OAUTH_TOKEN", // delegation, delivery — before that version is promoted to the image
}), // every mission uses. They carry the same Anthropic subscription
// credential as `claude`, because testing a new CLI against a different
// provider would not be testing the thing we are about to ship.
//
// Named rather than pattern-matched on anything looser: an unrecognised
// backend must still be refused at launch, which is what
// `backend_can_run_a_mission` and the harness's negative control assert.
None | Some("") | Some("default") | Some("claude") | Some("canary-claude") => {
Ok(Credential {
source: "CLAUDE_CODE_OAUTH_TOKEN",
target: "CLAUDE_CODE_OAUTH_TOKEN",
})
}
// GLM. `images/agent-glm` bakes `ANTHROPIC_BASE_URL=https://api.z.ai/ // GLM. `images/agent-glm` bakes `ANTHROPIC_BASE_URL=https://api.z.ai/
// api/anthropic` into the rootfs, so the endpoint is a property of the // api/anthropic` into the rootfs, so the endpoint is a property of the
// image and the credential is a property of the turn. That split is what // image and the credential is a property of the turn. That split is what
@@ -1094,6 +1106,25 @@ async fn capture_outstanding_phases(pool: &sqlx::PgPool, mission_id: Uuid) -> Re
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
/// A canary backend runs the production path, so it needs the production
/// credential — and an UNKNOWN backend must still be refused.
///
/// The second half is the one that matters: `backend_can_run_a_mission` is
/// what stops a mission being launched against a rootfs no node has, and
/// widening the credential map is exactly how that guard gets softened by
/// accident.
#[test]
fn a_canary_backend_is_credentialed_but_an_unknown_one_is_not() {
assert!(backend_can_run_a_mission("canary-claude"));
assert!(backend_can_run_a_mission("claude"));
for unknown in ["test226", "definitely-not-built", "canary", "canary-"] {
assert!(
!backend_can_run_a_mission(unknown),
"{unknown} must be refused at launch"
);
}
}
use super::*; use super::*;
/// The regression guard for the whole subscription feature. /// The regression guard for the whole subscription feature.