diff --git a/crates/worker-runtime/src/http_server.rs b/crates/worker-runtime/src/http_server.rs index 5d04e4c6d..0c7177d7a 100644 --- a/crates/worker-runtime/src/http_server.rs +++ b/crates/worker-runtime/src/http_server.rs @@ -2347,8 +2347,8 @@ impl RuntimeHttpRestError { RuntimeError::WorkerExecutionRejected { result, .. } => startup_transfer(result), _ => None, }; - let message = if startup.is_some() { - "Worker startup failed".to_owned() + let message = if let Some(startup) = &startup { + worker::startup_failure::internal_startup_failure_summary(&startup.primary).to_owned() } else { error.to_string() }; diff --git a/crates/worker/src/startup_failure.rs b/crates/worker/src/startup_failure.rs index b0c4c59fa..9e76a56cf 100644 --- a/crates/worker/src/startup_failure.rs +++ b/crates/worker/src/startup_failure.rs @@ -120,6 +120,20 @@ pub fn reproject_internal_startup_failure( StartupFailureAttachment::from_projected(&projected) } +/// Fixed, identifier-free summary for an already authenticated internal caller. +/// Keep the required operation actionable without exposing the source's Display. +/// This neither projects a Browser cause nor chooses an HTTP status. +pub fn internal_startup_failure_summary(failure: &StartupFailureAttachment) -> &'static str { + if failure + .project_for::(&()) + .is_ok() + { + "Worker startup failed while registering Session (session.record)" + } else { + "Worker startup failed" + } +} + /// Dispatch by the domain attested in process, not by parsing attachment fields. pub(crate) fn persist_binding_startup_failure( store: &S, @@ -249,6 +263,36 @@ mod tests { WorkerStoreError, }; + #[test] + fn internal_summary_retains_only_attested_required_operation() { + use protocol::session_failure::{SessionPrincipal, SessionRegistrationCause}; + let session = protocol::session_failure::session_registration_attachment( + SessionRegistrationCause::denied(SessionPrincipal::Subject), + ) + .unwrap(); + assert_eq!( + internal_startup_failure_summary(&session), + "Worker startup failed while registering Session (session.record)" + ); + let runtime = runtime_startup_attachment(RuntimeStartupCause { + stage: StartupStage::Feature, + reason: RuntimeStartupReason::FeatureFailed, + }) + .unwrap(); + assert_eq!( + internal_startup_failure_summary(&runtime), + "Worker startup failed" + ); + let corrupt = serde_json::from_value(serde_json::json!( + "session.record denied TEST_ONLY_CREDENTIAL /private/path raw_http_body" + )) + .unwrap(); + assert_eq!( + internal_startup_failure_summary(&corrupt), + "Worker startup failed" + ); + } + struct LostSaveResponse(FsWorkerStore); impl WorkerMetadataStore for LostSaveResponse { fn write(&self, metadata: &WorkerMetadata) -> Result<(), WorkerStoreError> { diff --git a/crates/workspace-server/src/hosts.rs b/crates/workspace-server/src/hosts.rs index 2c1739cba..58e5871de 100644 --- a/crates/workspace-server/src/hosts.rs +++ b/crates/workspace-server/src/hosts.rs @@ -6772,8 +6772,11 @@ fn embedded_runtime_diagnostic(error: &EmbeddedRuntimeError) -> RuntimeDiagnosti ), }; if let EmbeddedRuntimeError::WorkerExecutionRejected { result, .. } = error { - if result.startup_failure.is_some() { - projection.message = "Embedded Runtime Worker startup failed".into(); + if let Some(primary) = &result.startup_failure { + projection.message = format!( + "Embedded Runtime: {}", + worker::startup_failure::internal_startup_failure_summary(primary) + ); } projection.startup = result.startup_failure.as_ref().and_then(|primary| { internal_startup_transfer(runtime_api::WorkerStartupFailure {