From 7a2fd4262d0b011b9389dea3c0b8f16e53461743 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Wed, 12 Aug 2026 18:51:13 +0300 Subject: [PATCH 1/2] fix: preserve workflow route workspace and service tier Agent: agent-chief-staff --- .../background_agent_live.rs | 37 +++++++++++++++++-- .../src/runtime/workflow_orchestrator.rs | 15 +++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/background_agent_live.rs b/codex-rs/app-server/src/request_processors/background_agent_live.rs index be01f7a3f..a2038c333 100644 --- a/codex-rs/app-server/src/request_processors/background_agent_live.rs +++ b/codex-rs/app-server/src/request_processors/background_agent_live.rs @@ -4208,10 +4208,7 @@ async fn resolve_background_agent_config( }) .and_then(Value::as_str) .map(str::to_string); - let service_tier = payload - .and_then(|payload| payload.get("serviceTier")) - .and_then(Value::as_str) - .map(|value| Some(value.to_string())); + let service_tier = background_agent_snapshot_service_tier(payload)?; let approval_policy = payload .and_then(|payload| payload.get("approvalPolicy")) .cloned() @@ -4319,6 +4316,17 @@ async fn resolve_background_agent_config( }) } +fn background_agent_snapshot_service_tier( + payload: Option<&serde_json::Map>, +) -> anyhow::Result>> { + payload + .and_then(|payload| payload.get("serviceTier")) + .cloned() + .map(serde_json::from_value::>) + .transpose() + .map_err(anyhow::Error::from) +} + fn validate_background_agent_initial_execution_snapshot( run: &BackgroundAgentRun, snapshot: &BackgroundAgentExecutionSnapshot, @@ -6451,6 +6459,27 @@ mod tests { ); } + #[test] + fn background_agent_snapshot_service_tier_preserves_explicit_null() { + assert_eq!(background_agent_snapshot_service_tier(None).unwrap(), None); + + let explicit_null_payload = json!({ + "serviceTier": null, + }); + assert_eq!( + background_agent_snapshot_service_tier(explicit_null_payload.as_object()).unwrap(), + Some(None) + ); + + let explicit_value_payload = json!({ + "serviceTier": "default", + }); + assert_eq!( + background_agent_snapshot_service_tier(explicit_value_payload.as_object()).unwrap(), + Some(Some("default".to_string())) + ); + } + #[test] fn launch_capability_requests_merge_server_defaults_and_reject_caller_duplicates() { let mut requests = vec![ diff --git a/codex-rs/state/src/runtime/workflow_orchestrator.rs b/codex-rs/state/src/runtime/workflow_orchestrator.rs index 6739206f4..df23db709 100644 --- a/codex-rs/state/src/runtime/workflow_orchestrator.rs +++ b/codex-rs/state/src/runtime/workflow_orchestrator.rs @@ -2267,6 +2267,10 @@ fn branch_execution_payload( let workspace_root = crate::runtime::managed_worktrees::path_to_db_string( provisioned_workspace.worktree_path.as_path(), ); + let workspace_mode = match provisioned_workspace.mode { + WorkflowWorkspaceMode::IsolatedWorktree => "isolated_worktree", + WorkflowWorkspaceMode::SharedRepository => "shared_repository", + }; json!({ "snapshotSource": "workflow/branch_admission", "workflowRunId": run.run_id.as_str(), @@ -2293,7 +2297,7 @@ fn branch_execution_payload( .auth_profile .as_deref() .map(|profile| StateRuntime::background_agent_identity_sha256(profile.as_bytes())), - "workspace": branch.workspace_json, + "workspace": json!({"mode": workspace_mode}), "workflowBootstrap": &candidate.bootstrap_json, "reviewContext": branch.review_context.map(|context| &context.state_json), "envSnapshotPolicy": "inherit-minimal", @@ -6913,6 +6917,15 @@ WHERE worktree_id = ? .await .expect("execution snapshot should load") .expect("execution snapshot should exist"); + assert_eq!( + Some("shared_repository"), + execution_snapshot + .payload_json + .get("workspace") + .and_then(|workspace| workspace.get("mode")) + .and_then(Value::as_str), + "shared non-Git branch must preserve the effective shared-repository workspace mode" + ); assert_eq!( Some( crate::runtime::managed_worktrees::path_to_db_string(source_cwd.as_path()).as_str() From 79ec4294559f027a4098153424ed92e167fa460b Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Wed, 12 Aug 2026 20:53:40 +0300 Subject: [PATCH 2/2] fix: preserve admitted workflow route semantics Agent: unresolved-account030 --- .../background_agent_live.rs | 35 +++++++++++++++---- .../src/runtime/workflow_orchestrator.rs | 3 -- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/background_agent_live.rs b/codex-rs/app-server/src/request_processors/background_agent_live.rs index a2038c333..15c46c81b 100644 --- a/codex-rs/app-server/src/request_processors/background_agent_live.rs +++ b/codex-rs/app-server/src/request_processors/background_agent_live.rs @@ -4319,11 +4319,22 @@ async fn resolve_background_agent_config( fn background_agent_snapshot_service_tier( payload: Option<&serde_json::Map>, ) -> anyhow::Result>> { - payload - .and_then(|payload| payload.get("serviceTier")) - .cloned() - .map(serde_json::from_value::>) - .transpose() + let Some(payload) = payload else { + return Ok(None); + }; + let Some(service_tier) = payload.get("serviceTier") else { + return Ok(None); + }; + // Workflow admission snapshots always include this field, using null to + // represent the absence of a requested tier. Preserve that absence so the + // loaded config cannot invent the explicit default tier and diverge from + // the immutable admission receipt. Other snapshots retain explicit null + // as the ConfigOverrides reset-to-default signal. + if service_tier.is_null() && payload.contains_key("routeReceipt") { + return Ok(None); + } + serde_json::from_value::>(service_tier.clone()) + .map(Some) .map_err(anyhow::Error::from) } @@ -6461,7 +6472,10 @@ mod tests { #[test] fn background_agent_snapshot_service_tier_preserves_explicit_null() { - assert_eq!(background_agent_snapshot_service_tier(None).unwrap(), None); + assert_eq!( + background_agent_snapshot_service_tier(/*payload*/ None).unwrap(), + None + ); let explicit_null_payload = json!({ "serviceTier": null, @@ -6478,6 +6492,15 @@ mod tests { background_agent_snapshot_service_tier(explicit_value_payload.as_object()).unwrap(), Some(Some("default".to_string())) ); + + let workflow_null_payload = json!({ + "serviceTier": null, + "routeReceipt": {}, + }); + assert_eq!( + background_agent_snapshot_service_tier(workflow_null_payload.as_object()).unwrap(), + None + ); } #[test] diff --git a/codex-rs/state/src/runtime/workflow_orchestrator.rs b/codex-rs/state/src/runtime/workflow_orchestrator.rs index df23db709..7aff71fa4 100644 --- a/codex-rs/state/src/runtime/workflow_orchestrator.rs +++ b/codex-rs/state/src/runtime/workflow_orchestrator.rs @@ -929,7 +929,6 @@ async fn admit_ready_workflow_branches_in_tx( candidate: &candidate, attempt: branch_attempt, route_receipt: &route_receipt, - workspace_json: workspace_json.as_ref(), provisioned_workspace: &provisioned_workspace, review_context: review_context.as_ref(), background_agent_run_id: background_agent_run_id.as_str(), @@ -1340,7 +1339,6 @@ struct BackgroundBranchRunCreate<'a> { candidate: &'a ReadyBranchCandidate, attempt: i64, route_receipt: &'a WorkflowRouteReceipt, - workspace_json: Option<&'a Value>, provisioned_workspace: &'a ProvisionedWorkflowWorkspace, review_context: Option<&'a WorkflowReviewBranchContext>, background_agent_run_id: &'a str, @@ -1725,7 +1723,6 @@ async fn create_background_branch_run_if_missing_in_tx( candidate, attempt, route_receipt, - workspace_json: _, provisioned_workspace, review_context: _, background_agent_run_id,