fix(missions): forward the subscription token into mission containers
Switching agents to claude_cli left missions hanging: the per-mission container had claude_cli configured but no credential, so `claude -p` waited forever. A phase sat at `running` for ten minutes with nothing in the logs — no error, because there is nothing to error on. The original subscription design assumed a persisted `claude /login` under a bind-mounted $HOME. That holds for the shared runtime and NOT for a mission container, which gets its own data dir and therefore no login. So subscription mode now forwards CLAUDE_CODE_OAUTH_TOKEN. The two Anthropic credentials remain mutually exclusive, and there is now a test asserting it in both directions: Claude Code ranks ANTHROPIC_API_KEY above the OAuth token, so shipping both bills the API while the deployment believes it is on the subscription — visible only on the invoice. Deployment: CLAWMATES_RUNTIME_AUTH=subscription and CLAUDE_CODE_OAUTH_TOKEN added to compose + .env on gw-04. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ac47dcbe94
commit
5232175c88
@@ -83,10 +83,18 @@ impl RuntimeAuth {
|
|||||||
///
|
///
|
||||||
/// The other three are unrelated providers with no subscription equivalent, so
|
/// The other three are unrelated providers with no subscription equivalent, so
|
||||||
/// they forward in both modes.
|
/// they forward in both modes.
|
||||||
|
///
|
||||||
|
/// In subscription mode `CLAUDE_CODE_OAUTH_TOKEN` forwards instead. The
|
||||||
|
/// original design assumed a persisted `claude /login` under a bind-mounted
|
||||||
|
/// `$HOME`, but a *mission* container gets its own data dir and therefore no
|
||||||
|
/// login — so the token has to travel. Missing it is not a loud failure:
|
||||||
|
/// `claude -p` simply hangs with no credential, which is what a phase stuck
|
||||||
|
/// at `running` for ten minutes looked like when this was first switched on.
|
||||||
pub fn forwarded_provider_keys(auth: RuntimeAuth) -> Vec<&'static str> {
|
pub fn forwarded_provider_keys(auth: RuntimeAuth) -> Vec<&'static str> {
|
||||||
let mut keys = vec!["GEMINI_API_KEY", "GROQ_API_KEY", "OPENAI_API_KEY"];
|
let mut keys = vec!["GEMINI_API_KEY", "GROQ_API_KEY", "OPENAI_API_KEY"];
|
||||||
if auth == RuntimeAuth::ApiKey {
|
match auth {
|
||||||
keys.push("ANTHROPIC_API_KEY");
|
RuntimeAuth::ApiKey => keys.push("ANTHROPIC_API_KEY"),
|
||||||
|
RuntimeAuth::Subscription => keys.push("CLAUDE_CODE_OAUTH_TOKEN"),
|
||||||
}
|
}
|
||||||
keys
|
keys
|
||||||
}
|
}
|
||||||
@@ -805,6 +813,29 @@ mod tests {
|
|||||||
for k in ["GEMINI_API_KEY", "GROQ_API_KEY", "OPENAI_API_KEY"] {
|
for k in ["GEMINI_API_KEY", "GROQ_API_KEY", "OPENAI_API_KEY"] {
|
||||||
assert!(keys.contains(&k), "{k} should still be forwarded");
|
assert!(keys.contains(&k), "{k} should still be forwarded");
|
||||||
}
|
}
|
||||||
|
// And the subscription credential MUST travel. A mission container
|
||||||
|
// has its own data dir, so unlike the shared runtime it has no
|
||||||
|
// persisted `claude /login` to fall back on. Without this the CLI
|
||||||
|
// has no credential and simply hangs — a phase stuck at `running`
|
||||||
|
// with nothing in the logs, which is exactly how this was found.
|
||||||
|
assert!(
|
||||||
|
keys.contains(&"CLAUDE_CODE_OAUTH_TOKEN"),
|
||||||
|
"subscription mode must forward the token; without it `claude -p` \
|
||||||
|
hangs with no credential. Forwarded: {keys:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The two credentials must never travel together: Claude Code would pick
|
||||||
|
/// the API key and bill it while the deployment believes it is on the
|
||||||
|
/// subscription.
|
||||||
|
#[test]
|
||||||
|
fn the_two_anthropic_credentials_are_mutually_exclusive() {
|
||||||
|
for mode in [RuntimeAuth::ApiKey, RuntimeAuth::Subscription] {
|
||||||
|
let keys = forwarded_provider_keys(mode);
|
||||||
|
let both = keys.contains(&"ANTHROPIC_API_KEY")
|
||||||
|
&& keys.contains(&"CLAUDE_CODE_OAUTH_TOKEN");
|
||||||
|
assert!(!both, "{mode:?} forwards both credentials: {keys:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default behaviour is unchanged, so a deployment that never opts in keeps
|
/// Default behaviour is unchanged, so a deployment that never opts in keeps
|
||||||
@@ -820,6 +851,10 @@ mod tests {
|
|||||||
] {
|
] {
|
||||||
assert!(keys.contains(&k), "{k} should be forwarded in api_key mode");
|
assert!(keys.contains(&k), "{k} should be forwarded in api_key mode");
|
||||||
}
|
}
|
||||||
|
assert!(
|
||||||
|
!keys.contains(&"CLAUDE_CODE_OAUTH_TOKEN"),
|
||||||
|
"api_key mode must not also ship the subscription token"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An unset or misspelled value must fall back to the *existing* behaviour.
|
/// An unset or misspelled value must fall back to the *existing* behaviour.
|
||||||
|
|||||||
Reference in New Issue
Block a user