@@ -3,6 +3,8 @@ use crate::app_server_session::ThreadSessionState;
33use crate :: read_session_model;
44use codex_app_server_protocol:: Thread ;
55use codex_protocol:: ThreadId ;
6+ use codex_protocol:: models:: PermissionProfile ;
7+ use codex_protocol:: protocol:: SandboxPolicy ;
68
79impl App {
810 pub ( super ) async fn sync_active_thread_permission_settings_to_cached_session ( & mut self ) {
@@ -16,12 +18,11 @@ impl App {
1618 . config
1719 . permissions
1820 . legacy_sandbox_policy ( self . config . cwd . as_path ( ) ) ;
19- let permission_profile = Some (
20- self . chat_widget
21- . config_ref ( )
22- . permissions
23- . permission_profile ( ) ,
24- ) ;
21+ let permission_profile = self
22+ . chat_widget
23+ . config_ref ( )
24+ . permissions
25+ . permission_profile ( ) ;
2526 let update_session = |session : & mut ThreadSessionState | {
2627 session. approval_policy = approval_policy;
2728 session. approvals_reviewer = approvals_reviewer;
@@ -48,10 +49,7 @@ impl App {
4849 thread_id : ThreadId ,
4950 thread : & Thread ,
5051 ) -> ThreadSessionState {
51- let sandbox_policy = self
52- . config
53- . permissions
54- . legacy_sandbox_policy ( self . config . cwd . as_path ( ) ) ;
52+ let sandbox_policy = self . active_legacy_sandbox_policy_for_cwd ( thread. cwd . as_path ( ) ) ;
5553 let mut session = self
5654 . primary_session_configured
5755 . clone ( )
@@ -66,7 +64,8 @@ impl App {
6664 approval_policy : self . config . permissions . approval_policy . value ( ) ,
6765 approvals_reviewer : self . config . approvals_reviewer ,
6866 sandbox_policy,
69- permission_profile : None ,
67+ permission_profile : self
68+ . active_legacy_permission_profile_for_cwd ( thread. cwd . as_path ( ) ) ,
7069 cwd : thread. cwd . clone ( ) ,
7170 instruction_source_paths : Vec :: new ( ) ,
7271 reasoning_effort : self . chat_widget . current_reasoning_effort ( ) ,
@@ -79,7 +78,9 @@ impl App {
7978 session. thread_name = thread. name . clone ( ) ;
8079 session. model_provider_id = thread. model_provider . clone ( ) ;
8180 session. cwd = thread. cwd . clone ( ) ;
82- session. permission_profile = None ;
81+ session. sandbox_policy = self . active_legacy_sandbox_policy_for_cwd ( thread. cwd . as_path ( ) ) ;
82+ session. permission_profile =
83+ self . active_legacy_permission_profile_for_cwd ( thread. cwd . as_path ( ) ) ;
8384 session. instruction_source_paths = Vec :: new ( ) ;
8485 session. rollout_path = thread. path . clone ( ) ;
8586 if let Some ( model) =
@@ -93,6 +94,18 @@ impl App {
9394 session. history_entry_count = 0 ;
9495 session
9596 }
97+
98+ fn active_legacy_permission_profile_for_cwd ( & self , cwd : & std:: path:: Path ) -> PermissionProfile {
99+ let sandbox_policy = self . active_legacy_sandbox_policy_for_cwd ( cwd) ;
100+ PermissionProfile :: from_legacy_sandbox_policy_for_cwd ( & sandbox_policy, cwd)
101+ }
102+
103+ fn active_legacy_sandbox_policy_for_cwd ( & self , cwd : & std:: path:: Path ) -> SandboxPolicy {
104+ self . chat_widget
105+ . config_ref ( )
106+ . permissions
107+ . legacy_sandbox_policy ( cwd)
108+ }
96109}
97110
98111#[ cfg( test) ]
@@ -128,7 +141,7 @@ mod tests {
128141 approval_policy : AskForApproval :: Never ,
129142 approvals_reviewer : ApprovalsReviewer :: User ,
130143 sandbox_policy : SandboxPolicy :: new_read_only_policy ( ) ,
131- permission_profile : None ,
144+ permission_profile : PermissionProfile :: read_only ( ) ,
132145 cwd : cwd. abs ( ) ,
133146 instruction_source_paths : Vec :: new ( ) ,
134147 reasoning_effort : None ,
@@ -202,7 +215,7 @@ mod tests {
202215 approval_policy : AskForApproval :: OnRequest ,
203216 approvals_reviewer : ApprovalsReviewer :: AutoReview ,
204217 sandbox_policy : expected_sandbox_policy,
205- permission_profile : Some ( expected_permission_profile) ,
218+ permission_profile : expected_permission_profile,
206219 ..main_session
207220 } ;
208221 assert_eq ! (
@@ -256,7 +269,7 @@ mod tests {
256269 NetworkSandboxPolicy :: Restricted ,
257270 ) ;
258271 let session = ThreadSessionState {
259- permission_profile : Some ( profile. clone ( ) ) ,
272+ permission_profile : profile. clone ( ) ,
260273 ..test_thread_session ( thread_id, test_path_buf ( "/tmp/main" ) )
261274 } ;
262275
@@ -276,7 +289,7 @@ mod tests {
276289
277290 let expected_session = ThreadSessionState {
278291 approval_policy : AskForApproval :: OnRequest ,
279- permission_profile : Some ( profile) ,
292+ permission_profile : profile,
280293 ..session
281294 } ;
282295 assert_eq ! (
@@ -295,4 +308,61 @@ mod tests {
295308 . clone ( ) ;
296309 assert_eq ! ( store_session, Some ( expected_session) ) ;
297310 }
311+
312+ #[ tokio:: test]
313+ async fn thread_read_fallback_uses_active_permission_settings ( ) {
314+ let mut app = make_test_app ( ) . await ;
315+ let primary_thread_id =
316+ ThreadId :: from_string ( "00000000-0000-0000-0000-000000000404" ) . expect ( "valid thread" ) ;
317+ let read_thread_id =
318+ ThreadId :: from_string ( "00000000-0000-0000-0000-000000000405" ) . expect ( "valid thread" ) ;
319+ let primary_session = ThreadSessionState {
320+ permission_profile : PermissionProfile :: workspace_write ( ) ,
321+ ..test_thread_session ( primary_thread_id, test_path_buf ( "/tmp/primary" ) )
322+ } ;
323+ let read_thread = Thread {
324+ id : read_thread_id. to_string ( ) ,
325+ forked_from_id : None ,
326+ preview : "read thread" . to_string ( ) ,
327+ ephemeral : false ,
328+ model_provider : "read-provider" . to_string ( ) ,
329+ created_at : 1 ,
330+ updated_at : 2 ,
331+ status : codex_app_server_protocol:: ThreadStatus :: Idle ,
332+ path : None ,
333+ cwd : test_path_buf ( "/tmp/read" ) . abs ( ) ,
334+ cli_version : "0.0.0" . to_string ( ) ,
335+ source : codex_app_server_protocol:: SessionSource :: Unknown ,
336+ agent_nickname : None ,
337+ agent_role : None ,
338+ git_info : None ,
339+ name : Some ( "read thread" . to_string ( ) ) ,
340+ turns : Vec :: new ( ) ,
341+ } ;
342+
343+ app. primary_session_configured = Some ( primary_session. clone ( ) ) ;
344+ app. chat_widget . handle_thread_session ( primary_session) ;
345+
346+ let session = app
347+ . session_state_for_thread_read ( read_thread_id, & read_thread)
348+ . await ;
349+
350+ let expected_sandbox_policy = app
351+ . chat_widget
352+ . config_ref ( )
353+ . permissions
354+ . legacy_sandbox_policy ( read_thread. cwd . as_path ( ) ) ;
355+ let expected_permission_profile = PermissionProfile :: from_legacy_sandbox_policy_for_cwd (
356+ & expected_sandbox_policy,
357+ read_thread. cwd . as_path ( ) ,
358+ ) ;
359+ assert_eq ! ( session. sandbox_policy, expected_sandbox_policy) ;
360+ assert_eq ! ( session. permission_profile, expected_permission_profile) ;
361+ assert_ne ! (
362+ session. permission_profile,
363+ app. config. permissions. permission_profile( ) ,
364+ "thread/read fallback must use the active widget permissions rather than stale app \
365+ config defaults"
366+ ) ;
367+ }
298368}
0 commit comments