Fix TUI app-server permission profile conversions (#16284)

Addresses #16283

Problem: TUI app-server permission approvals could drop filesystem
grants because request and response payloads were round-tripped through
mismatched camelCase and snake_case JSON shapes.
Solution: Replace the lossy JSON round-trips with typed app-server/core
permission conversions so requested and granted permission profiles,
including filesystem paths and scope, are preserved end to end.
This commit is contained in:
Eric Traut
2026-04-01 22:00:27 -06:00
committed by GitHub
Unverified
parent d1068e057a
commit cb9ef06ecc
15 changed files with 602 additions and 60 deletions
+42 -17
View File
@@ -2,10 +2,10 @@ use std::collections::HashMap;
use crate::app_command::AppCommand;
use crate::app_command::AppCommandView;
use crate::app_server_approval_conversions::granted_permission_profile_from_request;
use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;
use codex_app_server_protocol::FileChangeApprovalDecision;
use codex_app_server_protocol::FileChangeRequestApprovalResponse;
use codex_app_server_protocol::GrantedPermissionProfile;
use codex_app_server_protocol::McpServerElicitationAction;
use codex_app_server_protocol::McpServerElicitationRequestResponse;
use codex_app_server_protocol::PermissionsRequestApprovalResponse;
@@ -153,14 +153,9 @@ impl PendingAppServerRequests {
Ok::<AppServerRequestResolution, String>(AppServerRequestResolution {
request_id,
result: serde_json::to_value(PermissionsRequestApprovalResponse {
permissions: serde_json::from_value::<GrantedPermissionProfile>(
serde_json::to_value(&response.permissions).map_err(|err| {
format!("failed to encode granted permissions: {err}")
})?,
)
.map_err(|err| {
format!("failed to decode granted permissions for app-server: {err}")
})?,
permissions: granted_permission_profile_from_request(
response.permissions.clone(),
),
scope: response.scope.into(),
})
.map_err(|err| {
@@ -276,6 +271,8 @@ fn file_change_decision(decision: &ReviewDecision) -> Result<FileChangeApprovalD
#[cfg(test)]
mod tests {
use super::PendingAppServerRequests;
use codex_app_server_protocol::AdditionalFileSystemPermissions;
use codex_app_server_protocol::AdditionalNetworkPermissions;
use codex_app_server_protocol::CommandExecutionRequestApprovalParams;
use codex_app_server_protocol::FileChangeRequestApprovalParams;
use codex_app_server_protocol::McpElicitationObjectType;
@@ -293,11 +290,16 @@ mod tests {
use codex_protocol::approvals::ElicitationAction;
use codex_protocol::approvals::ExecPolicyAmendment;
use codex_protocol::mcp::RequestId as McpRequestId;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::request_permissions::RequestPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::BTreeMap;
use std::path::PathBuf;
#[test]
fn resolves_exec_approval_through_app_server_request_id() {
@@ -339,6 +341,19 @@ mod tests {
#[test]
fn resolves_permissions_and_user_input_through_app_server_request_id() {
let mut pending = PendingAppServerRequests::default();
let read_path = if cfg!(windows) {
r"C:\tmp\read-only"
} else {
"/tmp/read-only"
};
let write_path = if cfg!(windows) {
r"C:\tmp\write"
} else {
"/tmp/write"
};
let absolute_path = |path: &str| {
AbsolutePathBuf::try_from(PathBuf::from(path)).expect("path must be absolute")
};
assert_eq!(
pending.note_server_request(&ServerRequest::PermissionsRequestApproval {
@@ -373,10 +388,15 @@ mod tests {
.take_resolution(&Op::RequestPermissionsResponse {
id: "perm-1".to_string(),
response: codex_protocol::request_permissions::RequestPermissionsResponse {
permissions: serde_json::from_value(json!({
"network": { "enabled": null }
}))
.expect("valid permissions"),
permissions: RequestPermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions {
read: Some(vec![absolute_path(read_path)]),
write: Some(vec![absolute_path(write_path)]),
}),
},
scope: codex_protocol::request_permissions::PermissionGrantScope::Session,
},
})
@@ -387,10 +407,15 @@ mod tests {
serde_json::from_value::<PermissionsRequestApprovalResponse>(permissions.result)
.expect("permissions response should decode"),
PermissionsRequestApprovalResponse {
permissions: serde_json::from_value(json!({
"network": { "enabled": null }
}))
.expect("valid permissions"),
permissions: codex_app_server_protocol::GrantedPermissionProfile {
network: Some(AdditionalNetworkPermissions {
enabled: Some(true),
}),
file_system: Some(AdditionalFileSystemPermissions {
read: Some(vec![absolute_path(read_path)]),
write: Some(vec![absolute_path(write_path)]),
}),
},
scope: PermissionGrantScope::Session,
}
);