mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 17:10:50 -10:00
Keep the session online after a generic fatal engine error
Refs #6561 (U02-01; the audit tracker stays open). apply_engine_error_to_app set offline_mode for every non-recoverable error. Three engine paths emit a fatal authentication error without starting or completing a model turn (a finished background shell that cannot resolve its route, a goal continuation that cannot resolve its route, and a child completion that cannot resolve the resume route), so no TurnComplete follows and neither lifecycle clearer runs. The flag then queued every later submit and restored every explicitly selected queued message instead of sending it, so the routing refusal blocked the next healthy attempt from even reaching route validation. Only the generic setter is removed. The environment-only authentication branch still sets offline_mode, reopens provider setup and posts its targeted notice; explicit offline onboarding, provider setup completion and failed-switch rollback are untouched. The fatal error still finalizes the same cells, ends loading, records failed status for an active turn and leaves the categorized error in history. A still-broken route can fail again and the existing recovery path keeps the unsent message; there is no automatic retry or provider rotation. Two existing expectations flip and one existing test is renamed (non_recoverable_engine_error_enters_offline_mode to ..._stays_online); no new test was added. The comments that described the old behaviour on the offline field, the error projection, the renamed test and fatal_auth are corrected. Verified locally (macOS; no hosted CI): frozen tui lib nextest batch, 426 run, 426 passed, 0 failed, 14876 skipped, shared with the MCP change. Every named existing test matched: non_recoverable_engine_error_stays_online, keyless_engine_error_stays_visible_after_a_config_ack, env_only_auth_failure_reopens_provider_onboarding, recoverable_engine_error_does_not_enter_offline_mode, offline_explore_selects_no_provider_and_writes_no_credential, the credential_ rejected_turn_ pair, credential_rejection_after_output_keeps_the_composer_empty, recoverable_stream_error_keeps_active_turn_for_pending_approval, provider_switch_auth_error_restores_previous_provider_and_model, real_engine_client_preflight_failure_leaves_dispatch_state_atomic, immediate_submit_closed_mailbox_restores_composer_and_skill and submit_disposition_scenario (1 each), plus 8 engine_error tests. Not verified: the trap itself was not reproduced in the running product. A TUI run against a local daemon that answers every chat request with 401 shows the in-turn authentication error and the existing resend note, which does not take the no-TurnComplete path, so it cannot tell the old and new behaviour apart. The three fatal paths above are known from source, not from a live run. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
fa659b9858
commit
7d84d0be6b
4 files changed
+10
-17
No files matched your search
@@ -132,7 +132,7 @@ impl ErrorEnvelope {
|
||||
)
|
||||
}
|
||||
|
||||
/// Authentication failure — fatal and blocks the session.
|
||||
/// Authentication failure — fatal for the attempted operation.
|
||||
#[must_use]
|
||||
pub fn fatal_auth(message: impl Into<String>) -> Self {
|
||||
Self::new(
|
||||
|
||||
@@ -1652,7 +1652,7 @@ pub struct App {
|
||||
/// each TurnStarted; background suggestion tasks capture the token and
|
||||
/// discard their result if the token no longer matches.
|
||||
pub prompt_suggestion_gen: std::sync::atomic::AtomicU64,
|
||||
/// Degraded connectivity mode; new user inputs are queued for later retry.
|
||||
/// Queue new submissions while browsing offline or recovering provider setup.
|
||||
pub offline_mode: bool,
|
||||
/// Whether an `EngineEvent::Error` has already been posted for the
|
||||
/// current turn. Suppresses the redundant "Turn failed:" status line
|
||||
|
||||
@@ -178,11 +178,8 @@ pub(crate) fn apply_picker_session_rename_to_active_app(
|
||||
/// Translate an `EngineEvent::Error` into UI state updates.
|
||||
///
|
||||
/// The engine's `recoverable` flag (mirrored on `ErrorEnvelope`) decides
|
||||
/// whether the session flips into offline mode: stream stalls, chunk
|
||||
/// timeouts, transient network errors, and rate-limit/server hiccups arrive
|
||||
/// recoverable and must NOT flip into offline. Hard failures (auth, billing,
|
||||
/// invalid request) arrive non-recoverable; those flip offline so subsequent
|
||||
/// messages get queued instead of silently lost mid-flight.
|
||||
/// whether an active turn stays live. Only an environment-only authentication
|
||||
/// failure enables offline queueing here while reopening provider setup.
|
||||
///
|
||||
/// `severity` drives transcript color: red for `Error`/`Critical`, amber for
|
||||
/// `Warning`, dim for `Info`.
|
||||
@@ -324,9 +321,6 @@ pub(crate) fn apply_engine_error_to_app(
|
||||
);
|
||||
return;
|
||||
}
|
||||
if !recoverable {
|
||||
app.offline_mode = true;
|
||||
}
|
||||
// Error is already in the transcript as HistoryCell::Error above;
|
||||
// don't emit a redundant status_message that would become a sticky
|
||||
// toast in the footer — that duplicates the transcript entry.
|
||||
|
||||
@@ -28488,11 +28488,10 @@ async fn agent_complete_clears_child_pending() {
|
||||
assert_eq!(rows.len(), 1);
|
||||
}
|
||||
|
||||
/// Hard failures (auth, billing, malformed request) DO need to flip offline
|
||||
/// mode so subsequent typed messages get queued instead of silently lost
|
||||
/// against a broken upstream.
|
||||
/// Hard failures leave the session online for a retry; an error without
|
||||
/// TurnComplete must not strand subsequent messages offline.
|
||||
#[test]
|
||||
fn non_recoverable_engine_error_enters_offline_mode() {
|
||||
fn non_recoverable_engine_error_stays_online() {
|
||||
use crate::error_taxonomy::ErrorEnvelope;
|
||||
let mut app = create_test_app();
|
||||
assert!(!app.offline_mode);
|
||||
@@ -28506,8 +28505,8 @@ fn non_recoverable_engine_error_enters_offline_mode() {
|
||||
);
|
||||
|
||||
assert!(
|
||||
app.offline_mode,
|
||||
"non-recoverable error must enter offline mode"
|
||||
!app.offline_mode,
|
||||
"non-recoverable error must keep the session online for a retry"
|
||||
);
|
||||
assert!(!app.is_loading);
|
||||
assert_eq!(app.runtime_turn_status.as_deref(), Some("failed"));
|
||||
@@ -28643,7 +28642,7 @@ async fn keyless_engine_error_stays_visible_after_a_config_ack() {
|
||||
}
|
||||
}
|
||||
drop(events);
|
||||
assert!(app.offline_mode);
|
||||
assert!(!app.offline_mode);
|
||||
assert!(!app.is_loading);
|
||||
for (width, height) in [(80, 24), (140, 40)] {
|
||||
let frame = render_test_app(&mut app, &config, width, height);
|
||||
|
||||
Reference in new issue
Block a user