diff --git a/src/orchestrator/mod.rs b/src/orchestrator/mod.rs index 5e750ddf..b72f90ee 100644 --- a/src/orchestrator/mod.rs +++ b/src/orchestrator/mod.rs @@ -51,6 +51,15 @@ use crate::db::Database; use crate::llm::LlmProvider; use crate::secrets::SecretsStore; +/// Resolve the orchestrator port from the `ORCHESTRATOR_PORT` environment +/// variable, falling back to 50051. +fn resolve_orchestrator_port() -> u16 { + std::env::var("ORCHESTRATOR_PORT") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(50051) +} + /// Result of orchestrator setup, containing all handles needed by the agent. pub struct OrchestratorSetup { pub container_job_manager: Option>, @@ -101,11 +110,12 @@ pub async fn setup_orchestrator( let job_event_tx = Some(tx); let token_store = TokenStore::new(); + let orchestrator_port = resolve_orchestrator_port(); let job_config = ContainerJobConfig { image: config.sandbox.image.clone(), memory_limit_mb: config.sandbox.memory_limit_mb, cpu_shares: config.sandbox.cpu_shares, - orchestrator_port: 50051, + orchestrator_port, claude_code_api_key: std::env::var("ANTHROPIC_API_KEY").ok(), claude_code_oauth_token: crate::config::ClaudeCodeConfig::extract_oauth_token(), claude_code_model: config.claude_code.model.clone(), @@ -127,7 +137,7 @@ pub async fn setup_orchestrator( }; tokio::spawn(async move { - if let Err(e) = OrchestratorApi::start(orchestrator_state, 50051).await { + if let Err(e) = OrchestratorApi::start(orchestrator_state, orchestrator_port).await { tracing::error!("Orchestrator API failed: {}", e); } }); @@ -151,3 +161,40 @@ pub async fn setup_orchestrator( docker_status, } } + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use super::*; + + /// Serialize access to `ORCHESTRATOR_PORT` env var across test threads. + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn resolve_orchestrator_port_from_env() { + let _guard = ENV_LOCK.lock().unwrap(); + + // Safety: env-var mutation requires unsafe in edition 2024; + // ENV_LOCK serializes concurrent access from other test threads. + + // Absent env var → default 50051 + unsafe { std::env::remove_var("ORCHESTRATOR_PORT") }; + assert_eq!(resolve_orchestrator_port(), 50051); + + // Valid custom port + unsafe { std::env::set_var("ORCHESTRATOR_PORT", "50052") }; + assert_eq!(resolve_orchestrator_port(), 50052); + + // Non-numeric value → fallback to default + unsafe { std::env::set_var("ORCHESTRATOR_PORT", "not_a_port") }; + assert_eq!(resolve_orchestrator_port(), 50051); + + // Out of u16 range → fallback to default + unsafe { std::env::set_var("ORCHESTRATOR_PORT", "99999") }; + assert_eq!(resolve_orchestrator_port(), 50051); + + // Cleanup + unsafe { std::env::remove_var("ORCHESTRATOR_PORT") }; + } +}