@@ -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,8 @@ 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 ( ) ) ;
53+ let permission_profile = self . active_permission_profile ( ) ;
5554 let mut session = self
5655 . primary_session_configured
5756 . clone ( )
@@ -65,8 +64,8 @@ impl App {
6564 service_tier : self . chat_widget . current_service_tier ( ) ,
6665 approval_policy : self . config . permissions . approval_policy . value ( ) ,
6766 approvals_reviewer : self . config . approvals_reviewer ,
68- sandbox_policy,
69- permission_profile : None ,
67+ sandbox_policy : sandbox_policy . clone ( ) ,
68+ permission_profile : permission_profile . clone ( ) ,
7069 cwd : thread. cwd . clone ( ) ,
7170 instruction_source_paths : Vec :: new ( ) ,
7271 reasoning_effort : self . chat_widget . current_reasoning_effort ( ) ,
@@ -79,7 +78,8 @@ 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 = sandbox_policy;
82+ session. permission_profile = permission_profile;
8383 session. instruction_source_paths = Vec :: new ( ) ;
8484 session. rollout_path = thread. path . clone ( ) ;
8585 if let Some ( model) =
@@ -93,6 +93,20 @@ impl App {
9393 session. history_entry_count = 0 ;
9494 session
9595 }
96+
97+ fn active_permission_profile ( & self ) -> PermissionProfile {
98+ self . chat_widget
99+ . config_ref ( )
100+ . permissions
101+ . permission_profile ( )
102+ }
103+
104+ fn active_legacy_sandbox_policy_for_cwd ( & self , cwd : & std:: path:: Path ) -> SandboxPolicy {
105+ self . chat_widget
106+ . config_ref ( )
107+ . permissions
108+ . legacy_sandbox_policy ( cwd)
109+ }
96110}
97111
98112#[ cfg( test) ]
@@ -128,7 +142,7 @@ mod tests {
128142 approval_policy : AskForApproval :: Never ,
129143 approvals_reviewer : ApprovalsReviewer :: User ,
130144 sandbox_policy : SandboxPolicy :: new_read_only_policy ( ) ,
131- permission_profile : None ,
145+ permission_profile : PermissionProfile :: read_only ( ) ,
132146 cwd : cwd. abs ( ) ,
133147 instruction_source_paths : Vec :: new ( ) ,
134148 reasoning_effort : None ,
@@ -202,7 +216,7 @@ mod tests {
202216 approval_policy : AskForApproval :: OnRequest ,
203217 approvals_reviewer : ApprovalsReviewer :: AutoReview ,
204218 sandbox_policy : expected_sandbox_policy,
205- permission_profile : Some ( expected_permission_profile) ,
219+ permission_profile : expected_permission_profile,
206220 ..main_session
207221 } ;
208222 assert_eq ! (
@@ -256,7 +270,7 @@ mod tests {
256270 NetworkSandboxPolicy :: Restricted ,
257271 ) ;
258272 let session = ThreadSessionState {
259- permission_profile : Some ( profile. clone ( ) ) ,
273+ permission_profile : profile. clone ( ) ,
260274 ..test_thread_session ( thread_id, test_path_buf ( "/tmp/main" ) )
261275 } ;
262276
@@ -276,7 +290,7 @@ mod tests {
276290
277291 let expected_session = ThreadSessionState {
278292 approval_policy : AskForApproval :: OnRequest ,
279- permission_profile : Some ( profile) ,
293+ permission_profile : profile,
280294 ..session
281295 } ;
282296 assert_eq ! (
@@ -295,4 +309,62 @@ mod tests {
295309 . clone ( ) ;
296310 assert_eq ! ( store_session, Some ( expected_session) ) ;
297311 }
312+
313+ #[ tokio:: test]
314+ async fn thread_read_fallback_uses_active_permission_settings ( ) {
315+ let mut app = make_test_app ( ) . await ;
316+ let primary_thread_id =
317+ ThreadId :: from_string ( "00000000-0000-0000-0000-000000000404" ) . expect ( "valid thread" ) ;
318+ let read_thread_id =
319+ ThreadId :: from_string ( "00000000-0000-0000-0000-000000000405" ) . expect ( "valid thread" ) ;
320+ let primary_session = ThreadSessionState {
321+ permission_profile : PermissionProfile :: workspace_write ( ) ,
322+ ..test_thread_session ( primary_thread_id, test_path_buf ( "/tmp/primary" ) )
323+ } ;
324+ let read_thread = Thread {
325+ id : read_thread_id. to_string ( ) ,
326+ forked_from_id : None ,
327+ preview : "read thread" . to_string ( ) ,
328+ ephemeral : false ,
329+ model_provider : "read-provider" . to_string ( ) ,
330+ created_at : 1 ,
331+ updated_at : 2 ,
332+ status : codex_app_server_protocol:: ThreadStatus :: Idle ,
333+ path : None ,
334+ cwd : test_path_buf ( "/tmp/read" ) . abs ( ) ,
335+ cli_version : "0.0.0" . to_string ( ) ,
336+ source : codex_app_server_protocol:: SessionSource :: Unknown ,
337+ agent_nickname : None ,
338+ agent_role : None ,
339+ git_info : None ,
340+ name : Some ( "read thread" . to_string ( ) ) ,
341+ turns : Vec :: new ( ) ,
342+ } ;
343+
344+ app. primary_session_configured = Some ( primary_session. clone ( ) ) ;
345+ app. chat_widget . handle_thread_session ( primary_session) ;
346+
347+ let session = app
348+ . session_state_for_thread_read ( read_thread_id, & read_thread)
349+ . await ;
350+
351+ let expected_sandbox_policy = app
352+ . chat_widget
353+ . config_ref ( )
354+ . permissions
355+ . legacy_sandbox_policy ( read_thread. cwd . as_path ( ) ) ;
356+ let expected_permission_profile = app
357+ . chat_widget
358+ . config_ref ( )
359+ . permissions
360+ . permission_profile ( ) ;
361+ assert_eq ! ( session. sandbox_policy, expected_sandbox_policy) ;
362+ assert_eq ! ( session. permission_profile, expected_permission_profile) ;
363+ assert_ne ! (
364+ session. permission_profile,
365+ app. config. permissions. permission_profile( ) ,
366+ "thread/read fallback must use the active widget permissions rather than stale app \
367+ config defaults"
368+ ) ;
369+ }
298370}
0 commit comments