fix(microvm): the relay URL must survive the login profile; deploy.sh must not ship a target symlink
- The first relayed kimi mission (01a0cfc3) bound the node relay, yet the guest dialled api.kimi.com through egress with the proxy token: the turn runs in a login shell that sources /etc/profile.d/00-image-env.sh (the image's ENV), which re-exports the glm/kimi images' baked ANTHROPIC_BASE_URL over ours. The turn command now re-exports the base URL after the profile. No key leaked — the guest had only the token — but relayed kimi/glm missions could not run. - deploy.sh excluded `target/`, which matches only a directory; a workstation whose target is a symlink copied it to the build host, breaking its builds. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
af70c5416e
commit
eb1137ed68
@@ -405,6 +405,23 @@ fn agent_command(prompt: &str, settings: Option<&str>) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// Re-assert the base URL inside the command, after the login shell's profile.
|
||||
///
|
||||
/// fcagent passes the turn's env into the process, but the command runs in a
|
||||
/// login shell that then sources `/etc/profile.d/00-image-env.sh` — written at
|
||||
/// rootfs build time from the image's `ENV` — and the glm and kimi images bake
|
||||
/// `ANTHROPIC_BASE_URL` there. MEASURED on the first relayed kimi mission
|
||||
/// (01a0cfc3): the node relay was bound, yet the guest dialled `api.kimi.com`
|
||||
/// through egress with the proxy token, because the profile overwrote the relay
|
||||
/// URL. The claude image bakes none, which is why that backend worked. The URL
|
||||
/// is not a secret, so it goes in the command; credentials stay env-only.
|
||||
fn with_base_url_override(cmd: String, base_url: Option<&str>) -> String {
|
||||
match base_url {
|
||||
Some(url) => format!("export ANTHROPIC_BASE_URL={} && {cmd}", shell_quote(url)),
|
||||
None => cmd,
|
||||
}
|
||||
}
|
||||
|
||||
/// How often the tap is drained while a turn runs.
|
||||
///
|
||||
/// A turn can last an hour; the World is meant to show what is happening now.
|
||||
@@ -862,7 +879,10 @@ async fn run_inside(
|
||||
// Concurrency here is safe because `fcagent` is thread-per-connection: the
|
||||
// live log tail already relies on exactly that, on a second connection, for
|
||||
// the whole length of a turn. So this needs no fleet-node change.
|
||||
let turn_cmd = agent_command(&prompt, settings.as_deref());
|
||||
let turn_cmd = with_base_url_override(
|
||||
agent_command(&prompt, settings.as_deref()),
|
||||
env.iter().find(|(k, _)| k == "ANTHROPIC_BASE_URL").map(|(_, v)| v.as_str()),
|
||||
);
|
||||
let turn = vm.exec_attributed(
|
||||
&turn_cmd,
|
||||
None,
|
||||
@@ -1120,6 +1140,17 @@ mod tests {
|
||||
/// live, and the command's own stdout is what becomes `VmOutcome::summary`.
|
||||
/// A redirect would give a live view and an empty summary — which is the
|
||||
/// same "green and empty" shape this codebase keeps finding.
|
||||
/// The relay URL must win over the image's profile, which the login shell
|
||||
/// sources after fcagent sets the env; and a non-relayed turn is untouched.
|
||||
#[test]
|
||||
fn a_relayed_turn_re_exports_the_base_url_after_the_profile() {
|
||||
let base = agent_command("t", None);
|
||||
let cmd = with_base_url_override(base.clone(), Some("http://127.0.0.1:11434/kimi"));
|
||||
assert!(cmd.starts_with("export ANTHROPIC_BASE_URL='http://127.0.0.1:11434/kimi' && "), "{cmd}");
|
||||
assert!(cmd.ends_with(&base));
|
||||
assert_eq!(with_base_url_override(base.clone(), None), base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_turn_is_teed_so_it_streams_and_still_reports() {
|
||||
let cmd = agent_command("do the thing", None);
|
||||
|
||||
Reference in New Issue
Block a user