Fix context folder timing issue in container workspaces

The MCP process can start before the mission's context file is written,
causing OPEN_AGENT_CONTEXT_ROOT to not be set. This results in uploaded
files being inaccessible inside containers.

Add read_runtime_context() to dynamically read the context file before
each container bash command, ensuring the context folder bind mount is
always configured correctly regardless of startup timing.
This commit is contained in:
Thomas Marchand
2026-01-17 16:16:49 +00:00
parent 4d89205844
commit b51dac9687

View File

@@ -20,6 +20,63 @@ use tokio::process::Command;
use super::{resolve_path_simple as resolve_path, Tool};
use crate::nspawn;
/// Context information read from the local context file.
/// This is re-read before each container command to handle timing issues
/// where the context file is written after the MCP process starts.
#[derive(Debug, Default)]
struct RuntimeContext {
context_root: Option<String>,
mission_id: Option<String>,
}
/// Read context information from the local context file or fall back to env vars.
/// This function is called before each container command to ensure we have the latest
/// context information, even if the context file was written after the MCP started.
fn read_runtime_context(container_root: &Path) -> RuntimeContext {
// First check env vars (set during MCP initialization)
let env_context_root = env::var("OPEN_AGENT_CONTEXT_ROOT").ok();
let env_mission_id = env::var("OPEN_AGENT_MISSION_ID").ok();
// If env vars are set, use them
if env_context_root.is_some() {
return RuntimeContext {
context_root: env_context_root,
mission_id: env_mission_id,
};
}
// Otherwise, try to read from the local context file
// This handles the case where the MCP started before the context file was written
let context_file = container_root.join(".openagent_context.json");
if let Ok(contents) = std::fs::read_to_string(&context_file) {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&contents) {
let context_root = json
.get("context_root")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let mission_id = json
.get("mission_id")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
// If we found context_root in the file, update the env var for future calls
if let Some(ref root) = context_root {
env::set_var("OPEN_AGENT_CONTEXT_ROOT", root);
}
if let Some(ref id) = mission_id {
env::set_var("OPEN_AGENT_MISSION_ID", id);
}
return RuntimeContext {
context_root,
mission_id,
};
}
}
RuntimeContext::default()
}
/// Sanitize command output to be safe for LLM consumption.
/// Removes binary garbage while preserving valid text.
fn sanitize_output(bytes: &[u8]) -> String {
@@ -476,12 +533,15 @@ async fn run_container_command(
args.push("--setenv=HOME=/root".to_string());
// Bind mission context into containers so uploaded files are accessible.
if let Ok(context_root) = env::var("OPEN_AGENT_CONTEXT_ROOT") {
// We read from the local context file to handle timing issues where the context file
// is written after the MCP process starts.
let runtime_ctx = read_runtime_context(&root);
if let Some(context_root) = runtime_ctx.context_root.as_ref() {
let context_root = context_root.trim();
if !context_root.is_empty() && Path::new(context_root).exists() {
args.push(format!("--bind={}:/root/context", context_root));
args.push("--setenv=OPEN_AGENT_CONTEXT_ROOT=/root/context".to_string());
if let Ok(mission_id) = env::var("OPEN_AGENT_MISSION_ID") {
if let Some(mission_id) = runtime_ctx.mission_id.as_ref() {
let mission_id = mission_id.trim();
if !mission_id.is_empty() {
args.push(format!(