From 840a758860150c6b6f31909140df3e7542d6edb7 Mon Sep 17 00:00:00 2001 From: James Devine Date: Wed, 6 May 2026 10:33:10 +0100 Subject: [PATCH 1/2] fix(execute): don't overwrite env-derived ADO context with None CLI args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_execution_context unconditionally replaced ado_org_url and ado_project (resolved by ExecutionContext::default() from SYSTEM_TEAMFOUNDATIONCOLLECTIONURI / SYSTEM_TEAMPROJECT) with the CLI argument values — which are None when the flags are omitted. This caused 'AZURE_DEVOPS_ORG_URL not set' errors for every safe output that needs an org URL during pipeline execution. Only override the env-derived values when the CLI flags are explicitly provided, and re-derive ado_organization when the URL is overridden. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/main.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 43ba366d..83b2197a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -245,8 +245,21 @@ async fn build_execution_context( .collect(); let mut ctx = crate::safeoutputs::ExecutionContext::default(); - ctx.ado_org_url = ado_org_url; - ctx.ado_project = ado_project; + // Only override env-derived values when CLI args are explicitly provided; + // otherwise keep the defaults from SYSTEM_TEAMFOUNDATIONCOLLECTIONURI / + // SYSTEM_TEAMPROJECT that ExecutionContext::default() already resolved. + if let Some(url) = ado_org_url { + ctx.ado_organization = url + .trim_end_matches('/') + .rsplit('/') + .next() + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()); + ctx.ado_org_url = Some(url); + } + if let Some(project) = ado_project { + ctx.ado_project = Some(project); + } ctx.working_directory = safe_output_dir.clone(); ctx.tool_configs = front_matter.safe_outputs.clone(); ctx.allowed_repositories = allowed_repositories; From d8fb969baf5dcc3798c28042f5f396e8a08ecbe2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 13:33:23 +0000 Subject: [PATCH 2/2] refactor(safeoutputs): extract org_from_url helper to eliminate duplication Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/d1969572-fd31-4c6f-85f0-cf5878796285 Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> --- src/main.rs | 7 +------ src/safeoutputs/mod.rs | 1 + src/safeoutputs/result.rs | 22 +++++++++++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 83b2197a..64147f83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -249,12 +249,7 @@ async fn build_execution_context( // otherwise keep the defaults from SYSTEM_TEAMFOUNDATIONCOLLECTIONURI / // SYSTEM_TEAMPROJECT that ExecutionContext::default() already resolved. if let Some(url) = ado_org_url { - ctx.ado_organization = url - .trim_end_matches('/') - .rsplit('/') - .next() - .filter(|s| !s.is_empty()) - .map(|s| s.to_string()); + ctx.ado_organization = crate::safeoutputs::org_from_url(&url); ctx.ado_org_url = Some(url); } if let Some(project) = ado_project { diff --git a/src/safeoutputs/mod.rs b/src/safeoutputs/mod.rs index fccfacd3..deb37ad7 100644 --- a/src/safeoutputs/mod.rs +++ b/src/safeoutputs/mod.rs @@ -288,6 +288,7 @@ pub use report_incomplete::*; pub use resolve_pr_thread::*; pub use result::{ ExecutionContext, ExecutionResult, Executor, ToolResult, Validate, anyhow_to_mcp_error, + org_from_url, }; pub use submit_pr_review::*; pub use update_pr::*; diff --git a/src/safeoutputs/result.rs b/src/safeoutputs/result.rs index b9ffb57a..d01cd2e2 100644 --- a/src/safeoutputs/result.rs +++ b/src/safeoutputs/result.rs @@ -129,6 +129,20 @@ impl ExecutionContext { } } +/// Extract the organization name from an Azure DevOps org URL. +/// +/// Handles both hosted (`https://dev.azure.com/myorg`) and on-prem +/// (`https://server/tfs/myorg`) URLs, with or without a trailing slash. +/// +/// Returns `None` if the URL is empty or has no meaningful last segment. +pub fn org_from_url(url: &str) -> Option { + url.trim_end_matches('/') + .rsplit('/') + .next() + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) +} + impl ExecutionContext { /// Build an `ExecutionContext` from an arbitrary env-var lookup function. /// @@ -144,13 +158,7 @@ impl ExecutionContext { .or_else(|| env("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI")); // Extract organization name from URL (e.g., "https://dev.azure.com/myorg/" -> "myorg") - let ado_organization = ado_org_url.as_ref().and_then(|url| { - url.trim_end_matches('/') - .rsplit('/') - .next() - .filter(|s| !s.is_empty()) - .map(|s| s.to_string()) - }); + let ado_organization = ado_org_url.as_ref().and_then(|url| org_from_url(url)); // Source directory is where git repos are checked out (BUILD_SOURCESDIRECTORY) let source_directory = env("BUILD_SOURCESDIRECTORY")