Skip to content

Commit cbeb8a2

Browse files
author
lijiuyang.5137
committed
Recover control restarts from stale session locks
1 parent 92de8b0 commit cbeb8a2

3 files changed

Lines changed: 359 additions & 10 deletions

File tree

src/runner.rs

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ async fn run_print(invocation: Invocation) -> Result<i32> {
179179
args: passthrough_args.clone(),
180180
session_id: current_session_id.clone(),
181181
invocation: &invocation,
182+
allow_session_strip_on_next_prepare: false,
182183
};
183184
run_stream_json(
184185
&mut process,
@@ -392,13 +393,15 @@ struct StreamSpawnContext<'a> {
392393
args: Vec<String>,
393394
session_id: Option<String>,
394395
invocation: &'a Invocation,
396+
allow_session_strip_on_next_prepare: bool,
395397
}
396398

397399
async fn ensure_sdk_mcp_runtime(
398400
process: &mut PtyProcess,
401+
tail: &mut TailCursor,
399402
input: &mut mpsc::Receiver<Value>,
400403
sdk_state: &mut SdkStreamState,
401-
spawn: &StreamSpawnContext<'_>,
404+
spawn: &mut StreamSpawnContext<'_>,
402405
) -> Result<bool> {
403406
if sdk_state.mcp_runtime.is_some() {
404407
return Ok(false);
@@ -429,7 +432,7 @@ async fn ensure_sdk_mcp_runtime(
429432
&spawn.session_id,
430433
spawn.invocation,
431434
)?;
432-
prepare_tty_for_prompt_with_mcp_retrying_session_lock(
435+
match prepare_tty_for_prompt_with_mcp_retrying_session_lock(
433436
process,
434437
input,
435438
&runtime,
@@ -438,7 +441,49 @@ async fn ensure_sdk_mcp_runtime(
438441
&rewritten_args,
439442
&spawn.session_id,
440443
)
441-
.await?;
444+
.await
445+
{
446+
Ok(()) => {
447+
spawn.allow_session_strip_on_next_prepare = false;
448+
}
449+
Err(error)
450+
if is_session_lock_startup_error(&error)
451+
&& spawn.allow_session_strip_on_next_prepare =>
452+
{
453+
let stripped_args = strip_session_resume_args(&spawn.args);
454+
if stripped_args == spawn.args {
455+
return Err(error);
456+
}
457+
let rewritten_stripped_args = args_with_mcp_runtime(&stripped_args, &runtime)?;
458+
logging::event(
459+
"tty_restart reason=control_update_session_lock action=strip_session_args",
460+
);
461+
process.terminate(PTY_TERMINATE_TIMEOUT);
462+
*process = spawn_tty_process(
463+
spawn.claude,
464+
spawn.cwd,
465+
spawn.env,
466+
&rewritten_stripped_args,
467+
&None,
468+
spawn.invocation,
469+
)?;
470+
prepare_tty_for_prompt_with_mcp_retrying_session_lock(
471+
process,
472+
input,
473+
&runtime,
474+
sdk_state,
475+
spawn,
476+
&rewritten_stripped_args,
477+
&None,
478+
)
479+
.await?;
480+
spawn.args = stripped_args;
481+
spawn.session_id = None;
482+
spawn.allow_session_strip_on_next_prepare = false;
483+
tail.reset_for_new_session();
484+
}
485+
Err(error) => return Err(error),
486+
}
442487
sdk_state.mcp_runtime = Some(runtime);
443488
Ok(true)
444489
}
@@ -483,7 +528,7 @@ async fn run_stream_json(
483528
Some("control_response") => {}
484529
Some("control_cancel_request") => {}
485530
Some("user") => {
486-
if ensure_sdk_mcp_runtime(process, &mut input, &mut sdk_state, spawn).await? {
531+
if ensure_sdk_mcp_runtime(process, tail, &mut input, &mut sdk_state, spawn).await? {
487532
tty_prepared = true;
488533
}
489534
if !tty_prepared {
@@ -521,7 +566,7 @@ async fn prepare_stream_tty_for_prompt_with_sdk_state(
521566
tail: &mut TailCursor,
522567
input: &mut mpsc::Receiver<Value>,
523568
sdk_state: &mut SdkStreamState,
524-
spawn: &StreamSpawnContext<'_>,
569+
spawn: &mut StreamSpawnContext<'_>,
525570
) -> Result<()> {
526571
let Some(runtime) = sdk_state.mcp_runtime.take() else {
527572
return prepare_stream_tty_for_prompt(process, tail, spawn).await;
@@ -537,7 +582,7 @@ async fn prepare_stream_tty_for_prompt_with_sdk_state(
537582
async fn prepare_stream_tty_for_prompt(
538583
process: &mut PtyProcess,
539584
tail: &mut TailCursor,
540-
spawn: &StreamSpawnContext<'_>,
585+
spawn: &mut StreamSpawnContext<'_>,
541586
) -> Result<()> {
542587
match prepare_tty_for_prompt_retrying_session_lock(
543588
process,
@@ -550,7 +595,46 @@ async fn prepare_stream_tty_for_prompt(
550595
)
551596
.await
552597
{
553-
Ok(()) => Ok(()),
598+
Ok(()) => {
599+
spawn.allow_session_strip_on_next_prepare = false;
600+
Ok(())
601+
}
602+
Err(error)
603+
if is_session_lock_startup_error(&error)
604+
&& spawn.allow_session_strip_on_next_prepare =>
605+
{
606+
let stripped_args = strip_session_resume_args(&spawn.args);
607+
if stripped_args == spawn.args {
608+
return Err(error);
609+
}
610+
logging::event(
611+
"tty_restart reason=control_update_session_lock action=strip_session_args",
612+
);
613+
process.terminate(PTY_TERMINATE_TIMEOUT);
614+
*process = spawn_tty_process(
615+
spawn.claude,
616+
spawn.cwd,
617+
spawn.env,
618+
&stripped_args,
619+
&None,
620+
spawn.invocation,
621+
)?;
622+
prepare_tty_for_prompt_retrying_session_lock(
623+
process,
624+
spawn.claude,
625+
spawn.cwd,
626+
spawn.env,
627+
&stripped_args,
628+
&None,
629+
spawn.invocation,
630+
)
631+
.await?;
632+
spawn.args = stripped_args;
633+
spawn.session_id = None;
634+
spawn.allow_session_strip_on_next_prepare = false;
635+
tail.reset_for_new_session();
636+
Ok(())
637+
}
554638
Err(error) if is_bad_resume_startup_error(&error) => {
555639
let stripped_args = strip_session_resume_args(&spawn.args);
556640
if stripped_args == spawn.args {
@@ -576,6 +660,9 @@ async fn prepare_stream_tty_for_prompt(
576660
spawn.invocation,
577661
)
578662
.await?;
663+
spawn.args = stripped_args;
664+
spawn.session_id = None;
665+
spawn.allow_session_strip_on_next_prepare = false;
579666
tail.reset_for_new_session();
580667
Ok(())
581668
}
@@ -589,7 +676,7 @@ async fn prepare_stream_tty_for_prompt_with_mcp_runtime(
589676
input: &mut mpsc::Receiver<Value>,
590677
runtime: &SdkMcpRuntime,
591678
sdk_state: &mut SdkStreamState,
592-
spawn: &StreamSpawnContext<'_>,
679+
spawn: &mut StreamSpawnContext<'_>,
593680
) -> Result<()> {
594681
let rewritten_spawn_args = args_with_mcp_runtime(&spawn.args, runtime)?;
595682
match prepare_tty_for_prompt_with_mcp_retrying_session_lock(
@@ -603,7 +690,47 @@ async fn prepare_stream_tty_for_prompt_with_mcp_runtime(
603690
)
604691
.await
605692
{
606-
Ok(()) => Ok(()),
693+
Ok(()) => {
694+
spawn.allow_session_strip_on_next_prepare = false;
695+
Ok(())
696+
}
697+
Err(error)
698+
if is_session_lock_startup_error(&error)
699+
&& spawn.allow_session_strip_on_next_prepare =>
700+
{
701+
let stripped_args = strip_session_resume_args(&spawn.args);
702+
if stripped_args == spawn.args {
703+
return Err(error);
704+
}
705+
let rewritten_args = args_with_mcp_runtime(&stripped_args, runtime)?;
706+
logging::event(
707+
"tty_restart reason=control_update_session_lock action=strip_session_args",
708+
);
709+
process.terminate(PTY_TERMINATE_TIMEOUT);
710+
*process = spawn_tty_process(
711+
spawn.claude,
712+
spawn.cwd,
713+
spawn.env,
714+
&rewritten_args,
715+
&None,
716+
spawn.invocation,
717+
)?;
718+
prepare_tty_for_prompt_with_mcp_retrying_session_lock(
719+
process,
720+
input,
721+
runtime,
722+
sdk_state,
723+
spawn,
724+
&rewritten_args,
725+
&None,
726+
)
727+
.await?;
728+
spawn.args = stripped_args;
729+
spawn.session_id = None;
730+
spawn.allow_session_strip_on_next_prepare = false;
731+
tail.reset_for_new_session();
732+
Ok(())
733+
}
607734
Err(error) if is_bad_resume_startup_error(&error) => {
608735
let stripped_args = strip_session_resume_args(&spawn.args);
609736
if stripped_args == spawn.args {
@@ -630,6 +757,9 @@ async fn prepare_stream_tty_for_prompt_with_mcp_runtime(
630757
&None,
631758
)
632759
.await?;
760+
spawn.args = stripped_args;
761+
spawn.session_id = None;
762+
spawn.allow_session_strip_on_next_prepare = false;
633763
tail.reset_for_new_session();
634764
Ok(())
635765
}

src/runner/sdk_control.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn control_request_string(value: &Value, keys: &[&str]) -> Option<String> {
8282
fn restart_tty_after_control_update(
8383
process: &mut PtyProcess,
8484
sdk_state: &SdkStreamState,
85-
spawn: &StreamSpawnContext<'_>,
85+
spawn: &mut StreamSpawnContext<'_>,
8686
tty_prepared: &mut bool,
8787
) -> Result<()> {
8888
let args = args_with_runtime_mcp(&spawn.args, sdk_state.mcp_runtime.as_ref())?;
@@ -99,6 +99,7 @@ fn restart_tty_after_control_update(
9999
&spawn.session_id,
100100
spawn.invocation,
101101
)?;
102+
spawn.allow_session_strip_on_next_prepare = true;
102103
*tty_prepared = false;
103104
Ok(())
104105
}

0 commit comments

Comments
 (0)