Remove panic/crash reporting from collab (#39249)

Conrad Irwin created

Crashes have been going to Sentry since v0.201.x

Release Notes:

- N/A

Change summary

Cargo.lock                             |   1 
Cargo.toml                             |   1 
crates/collab/.env.toml                |   2 
crates/collab/Cargo.toml               |   1 
crates/collab/k8s/collab.template.yml  |   5 
crates/collab/src/api.rs               |   2 
crates/collab/src/api/events.rs        | 446 ---------------------------
crates/collab/src/api/ips_file.rs      | 346 ---------------------
crates/collab/src/api/slack.rs         | 144 ---------
crates/collab/src/lib.rs               |   2 
crates/collab/src/tests/test_server.rs |   1 
11 files changed, 8 insertions(+), 943 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3385,7 +3385,6 @@ dependencies = [
  "reqwest 0.11.27",
  "reqwest_client",
  "rpc",
- "rustc-demangle",
  "scrypt",
  "sea-orm",
  "semantic_version",

Cargo.toml 🔗

@@ -620,7 +620,6 @@ runtimelib = {  git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804
     "async-dispatcher-runtime",
 ] }
 rust-embed = { version = "8.4", features = ["include-exclude"] }
-rustc-demangle = "0.1.23"
 rustc-hash = "2.1.0"
 rustls = { version = "0.23.26" }
 rustls-platform-verifier = "0.5.0"

crates/collab/.env.toml 🔗

@@ -20,7 +20,5 @@ LLM_DATABASE_MAX_CONNECTIONS = 5
 LLM_API_SECRET = "llm-secret"
 OPENAI_API_KEY = "llm-secret"
 
-# SLACK_PANICS_WEBHOOK = ""
-
 # RUST_LOG=info
 # LOG_JSON=true

crates/collab/Cargo.toml 🔗

@@ -46,7 +46,6 @@ rand.workspace = true
 reqwest = { version = "0.11", features = ["json"] }
 reqwest_client.workspace = true
 rpc.workspace = true
-rustc-demangle.workspace = true
 scrypt = "0.11"
 sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
 semantic_version.workspace = true

crates/collab/k8s/collab.template.yml 🔗

@@ -214,11 +214,6 @@ spec:
                 secretKeyRef:
                   name: blob-store
                   key: bucket
-            - name: SLACK_PANICS_WEBHOOK
-              valueFrom:
-                secretKeyRef:
-                  name: slack
-                  key: panics_webhook
             - name: COMPLETE_WITH_LANGUAGE_MODEL_RATE_LIMIT_PER_HOUR
               value: "1000"
             - name: SUPERMAVEN_ADMIN_API_KEY

crates/collab/src/api.rs 🔗

@@ -1,8 +1,6 @@
 pub mod contributors;
 pub mod events;
 pub mod extensions;
-pub mod ips_file;
-pub mod slack;
 
 use crate::{AppState, Error, Result, auth, db::UserId, rpc};
 use anyhow::Context as _;

crates/collab/src/api/events.rs 🔗

@@ -1,33 +1,28 @@
-use super::ips_file::IpsFile;
 use crate::api::CloudflareIpCountryHeader;
-use crate::{AppState, Error, Result, api::slack};
+use crate::{AppState, Error, Result};
 use anyhow::anyhow;
-use aws_sdk_s3::primitives::ByteStream;
 use axum::{
     Extension, Router, TypedHeader,
     body::Bytes,
     headers::Header,
-    http::{HeaderMap, HeaderName, StatusCode},
+    http::{HeaderName, StatusCode},
     routing::post,
 };
 use chrono::Duration;
-use semantic_version::SemanticVersion;
 use serde::{Deserialize, Serialize};
 use serde_json::json;
 use sha2::{Digest, Sha256};
 use std::sync::{Arc, OnceLock};
-use telemetry_events::{Event, EventRequestBody, Panic};
+use telemetry_events::{Event, EventRequestBody};
 use util::ResultExt;
 use uuid::Uuid;
 
-const CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
-
 pub fn router() -> Router {
     Router::new()
         .route("/telemetry/events", post(post_events))
-        .route("/telemetry/crashes", post(post_crash))
+        .route("/telemetry/crashes", post(post_panic))
         .route("/telemetry/panics", post(post_panic))
-        .route("/telemetry/hangs", post(post_hang))
+        .route("/telemetry/hangs", post(post_panic))
 }
 
 pub struct ZedChecksumHeader(Vec<u8>);
@@ -58,437 +53,12 @@ impl Header for ZedChecksumHeader {
     }
 }
 
-pub async fn post_crash(
-    Extension(app): Extension<Arc<AppState>>,
-    headers: HeaderMap,
-    body: Bytes,
-) -> Result<()> {
-    let report = IpsFile::parse(&body)?;
-    let version_threshold = SemanticVersion::new(0, 123, 0);
-
-    let bundle_id = &report.header.bundle_id;
-    let app_version = &report.app_version();
-
-    if bundle_id == "dev.zed.Zed-Dev" {
-        log::error!("Crash uploads from {} are ignored.", bundle_id);
-        return Ok(());
-    }
-
-    if app_version.is_none() || app_version.unwrap() < version_threshold {
-        log::error!(
-            "Crash uploads from {} are ignored.",
-            report.header.app_version
-        );
-        return Ok(());
-    }
-    let app_version = app_version.unwrap();
-
-    if let Some(blob_store_client) = app.blob_store_client.as_ref() {
-        let response = blob_store_client
-            .head_object()
-            .bucket(CRASH_REPORTS_BUCKET)
-            .key(report.header.incident_id.clone() + ".ips")
-            .send()
-            .await;
-
-        if response.is_ok() {
-            log::info!("We've already uploaded this crash");
-            return Ok(());
-        }
-
-        blob_store_client
-            .put_object()
-            .bucket(CRASH_REPORTS_BUCKET)
-            .key(report.header.incident_id.clone() + ".ips")
-            .acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
-            .body(ByteStream::from(body.to_vec()))
-            .send()
-            .await
-            .map_err(|e| log::error!("Failed to upload crash: {}", e))
-            .ok();
-    }
-
-    let recent_panic_on: Option<i64> = headers
-        .get("x-zed-panicked-on")
-        .and_then(|h| h.to_str().ok())
-        .and_then(|s| s.parse().ok());
-
-    let installation_id = headers
-        .get("x-zed-installation-id")
-        .and_then(|h| h.to_str().ok())
-        .map(|s| s.to_string())
-        .unwrap_or_default();
-
-    let mut recent_panic = None;
-
-    if let Some(recent_panic_on) = recent_panic_on {
-        let crashed_at = match report.timestamp() {
-            Ok(t) => Some(t),
-            Err(e) => {
-                log::error!("Can't parse {}: {}", report.header.timestamp, e);
-                None
-            }
-        };
-        if crashed_at.is_some_and(|t| (t.timestamp_millis() - recent_panic_on).abs() <= 30000) {
-            recent_panic = headers.get("x-zed-panic").and_then(|h| h.to_str().ok());
-        }
-    }
-
-    let description = report.description(recent_panic);
-    let summary = report.backtrace_summary();
-
-    tracing::error!(
-        service = "client",
-        version = %report.header.app_version,
-        os_version = %report.header.os_version,
-        bundle_id = %report.header.bundle_id,
-        incident_id = %report.header.incident_id,
-        installation_id = %installation_id,
-        description = %description,
-        backtrace = %summary,
-        "crash report"
-    );
-
-    if let Some(kinesis_client) = app.kinesis_client.clone()
-        && let Some(stream) = app.config.kinesis_stream.clone()
-    {
-        let properties = json!({
-            "app_version": report.header.app_version,
-            "os_version": report.header.os_version,
-            "os_name": "macOS",
-            "bundle_id": report.header.bundle_id,
-            "incident_id": report.header.incident_id,
-            "installation_id": installation_id,
-            "description": description,
-            "backtrace": summary,
-        });
-        let row = SnowflakeRow::new(
-            "Crash Reported",
-            None,
-            false,
-            Some(installation_id),
-            properties,
-        );
-        let data = serde_json::to_vec(&row)?;
-        kinesis_client
-            .put_record()
-            .stream_name(stream)
-            .partition_key(row.insert_id.unwrap_or_default())
-            .data(data.into())
-            .send()
-            .await
-            .log_err();
-    }
-
-    if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
-        let payload = slack::WebhookBody::new(|w| {
-            w.add_section(|s| s.text(slack::Text::markdown(description)))
-                .add_section(|s| {
-                    s.add_field(slack::Text::markdown(format!(
-                        "*Version:*\n{} ({})",
-                        bundle_id, app_version
-                    )))
-                    .add_field({
-                        let hostname = app.config.blob_store_url.clone().unwrap_or_default();
-                        let hostname = hostname.strip_prefix("https://").unwrap_or_else(|| {
-                            hostname.strip_prefix("http://").unwrap_or_default()
-                        });
-
-                        slack::Text::markdown(format!(
-                            "*Incident:*\n<https://{}.{}/{}.ips|{}…>",
-                            CRASH_REPORTS_BUCKET,
-                            hostname,
-                            report.header.incident_id,
-                            report
-                                .header
-                                .incident_id
-                                .chars()
-                                .take(8)
-                                .collect::<String>(),
-                        ))
-                    })
-                })
-                .add_rich_text(|r| r.add_preformatted(|p| p.add_text(summary)))
-        });
-        let payload_json = serde_json::to_string(&payload).map_err(|err| {
-            log::error!("Failed to serialize payload to JSON: {err}");
-            Error::Internal(anyhow!(err))
-        })?;
-
-        reqwest::Client::new()
-            .post(slack_panics_webhook)
-            .header("Content-Type", "application/json")
-            .body(payload_json)
-            .send()
-            .await
-            .map_err(|err| {
-                log::error!("Failed to send payload to Slack: {err}");
-                Error::Internal(anyhow!(err))
-            })?;
-    }
-
-    Ok(())
-}
-
-pub async fn post_hang(
-    Extension(app): Extension<Arc<AppState>>,
-    TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
-    body: Bytes,
-) -> Result<()> {
-    let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
-        return Err(Error::http(
-            StatusCode::INTERNAL_SERVER_ERROR,
-            "events not enabled".into(),
-        ))?;
-    };
-
-    if checksum != expected {
-        return Err(Error::http(
-            StatusCode::BAD_REQUEST,
-            "invalid checksum".into(),
-        ))?;
-    }
-
-    let incident_id = Uuid::new_v4().to_string();
-
-    // dump JSON into S3 so we can get frame offsets if we need to.
-    if let Some(blob_store_client) = app.blob_store_client.as_ref() {
-        blob_store_client
-            .put_object()
-            .bucket(CRASH_REPORTS_BUCKET)
-            .key(incident_id.clone() + ".hang.json")
-            .acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
-            .body(ByteStream::from(body.to_vec()))
-            .send()
-            .await
-            .map_err(|e| log::error!("Failed to upload crash: {}", e))
-            .ok();
-    }
-
-    let report: telemetry_events::HangReport = serde_json::from_slice(&body).map_err(|err| {
-        log::error!("can't parse report json: {err}");
-        Error::Internal(anyhow!(err))
-    })?;
-
-    let mut backtrace = "Possible hang detected on main thread:".to_string();
-    let unknown = "<unknown>".to_string();
-    for frame in report.backtrace.iter() {
-        backtrace.push_str(&format!("\n{}", frame.symbols.first().unwrap_or(&unknown)));
-    }
-
-    tracing::error!(
-        service = "client",
-        version = %report.app_version.unwrap_or_default().to_string(),
-        os_name = %report.os_name,
-        os_version = report.os_version.unwrap_or_default(),
-        incident_id = %incident_id,
-        installation_id = %report.installation_id.unwrap_or_default(),
-        backtrace = %backtrace,
-        "hang report");
-
-    Ok(())
-}
-
-pub async fn post_panic(
-    Extension(app): Extension<Arc<AppState>>,
-    TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
-    body: Bytes,
-) -> Result<()> {
-    let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
-        return Err(Error::http(
-            StatusCode::INTERNAL_SERVER_ERROR,
-            "events not enabled".into(),
-        ))?;
-    };
-
-    if checksum != expected {
-        return Err(Error::http(
-            StatusCode::BAD_REQUEST,
-            "invalid checksum".into(),
-        ))?;
-    }
-
-    let report: telemetry_events::PanicRequest = serde_json::from_slice(&body)
-        .map_err(|_| Error::http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
-    let incident_id = uuid::Uuid::new_v4().to_string();
-    let panic = report.panic;
-
-    if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
-        return Err(Error::http(
-            StatusCode::BAD_REQUEST,
-            "invalid os version".into(),
-        ))?;
-    }
-
-    if let Some(blob_store_client) = app.blob_store_client.as_ref() {
-        let response = blob_store_client
-            .head_object()
-            .bucket(CRASH_REPORTS_BUCKET)
-            .key(incident_id.clone() + ".json")
-            .send()
-            .await;
-
-        if response.is_ok() {
-            log::info!("We've already uploaded this crash");
-            return Ok(());
-        }
-
-        blob_store_client
-            .put_object()
-            .bucket(CRASH_REPORTS_BUCKET)
-            .key(incident_id.clone() + ".json")
-            .acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
-            .body(ByteStream::from(body.to_vec()))
-            .send()
-            .await
-            .map_err(|e| log::error!("Failed to upload crash: {}", e))
-            .ok();
-    }
-
-    let backtrace = panic.backtrace.join("\n");
-
-    tracing::error!(
-        service = "client",
-        version = %panic.app_version,
-        os_name = %panic.os_name,
-        os_version = %panic.os_version.clone().unwrap_or_default(),
-        incident_id = %incident_id,
-        installation_id = %panic.installation_id.clone().unwrap_or_default(),
-        description = %panic.payload,
-        backtrace = %backtrace,
-        "panic report"
-    );
-
-    if let Some(kinesis_client) = app.kinesis_client.clone()
-        && let Some(stream) = app.config.kinesis_stream.clone()
-    {
-        let properties = json!({
-            "app_version": panic.app_version,
-            "os_name": panic.os_name,
-            "os_version": panic.os_version,
-            "incident_id": incident_id,
-            "installation_id": panic.installation_id,
-            "description": panic.payload,
-            "backtrace": backtrace,
-        });
-        let row = SnowflakeRow::new(
-            "Panic Reported",
-            None,
-            false,
-            panic.installation_id.clone(),
-            properties,
-        );
-        let data = serde_json::to_vec(&row)?;
-        kinesis_client
-            .put_record()
-            .stream_name(stream)
-            .partition_key(row.insert_id.unwrap_or_default())
-            .data(data.into())
-            .send()
-            .await
-            .log_err();
-    }
-
-    if !report_to_slack(&panic) {
-        return Ok(());
-    }
-
-    if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
-        let backtrace = if panic.backtrace.len() > 25 {
-            let total = panic.backtrace.len();
-            format!(
-                "{}\n   and {} more",
-                panic
-                    .backtrace
-                    .iter()
-                    .take(20)
-                    .cloned()
-                    .collect::<Vec<_>>()
-                    .join("\n"),
-                total - 20
-            )
-        } else {
-            panic.backtrace.join("\n")
-        };
-        let backtrace_with_summary = panic.payload + "\n" + &backtrace;
-
-        let version = if panic.release_channel == "nightly"
-            && !panic.app_version.contains("remote-server")
-            && let Some(sha) = panic.app_commit_sha
-        {
-            format!("Zed Nightly {}", sha.chars().take(7).collect::<String>())
-        } else {
-            panic.app_version
-        };
-
-        let payload = slack::WebhookBody::new(|w| {
-            w.add_section(|s| s.text(slack::Text::markdown("Panic request".to_string())))
-                .add_section(|s| {
-                    s.add_field(slack::Text::markdown(format!("*Version:*\n {version} ",)))
-                        .add_field({
-                            let hostname = app.config.blob_store_url.clone().unwrap_or_default();
-                            let hostname = hostname.strip_prefix("https://").unwrap_or_else(|| {
-                                hostname.strip_prefix("http://").unwrap_or_default()
-                            });
-
-                            slack::Text::markdown(format!(
-                                "*{} {}:*\n<https://{}.{}/{}.json|{}…>",
-                                panic.os_name,
-                                panic.os_version.unwrap_or_default(),
-                                CRASH_REPORTS_BUCKET,
-                                hostname,
-                                incident_id,
-                                incident_id.chars().take(8).collect::<String>(),
-                            ))
-                        })
-                })
-                .add_rich_text(|r| r.add_preformatted(|p| p.add_text(backtrace_with_summary)))
-        });
-        let payload_json = serde_json::to_string(&payload).map_err(|err| {
-            log::error!("Failed to serialize payload to JSON: {err}");
-            Error::Internal(anyhow!(err))
-        })?;
-
-        reqwest::Client::new()
-            .post(slack_panics_webhook)
-            .header("Content-Type", "application/json")
-            .body(payload_json)
-            .send()
-            .await
-            .map_err(|err| {
-                log::error!("Failed to send payload to Slack: {err}");
-                Error::Internal(anyhow!(err))
-            })?;
-    }
-
+pub async fn post_panic() -> Result<()> {
+    // as of v0.201.x crash/panic reporting is now done via Sentry.
+    // The endpoint returns OK to avoid spurious errors for old clients.
     Ok(())
 }
 
-fn report_to_slack(panic: &Panic) -> bool {
-    // Panics on macOS should make their way to Slack as a crash report,
-    // so we don't need to send them a second time via this channel.
-    if panic.os_name == "macOS" {
-        return false;
-    }
-
-    if panic.payload.contains("ERROR_SURFACE_LOST_KHR") {
-        return false;
-    }
-
-    if panic.payload.contains("ERROR_INITIALIZATION_FAILED") {
-        return false;
-    }
-
-    if panic
-        .payload
-        .contains("GPU has crashed, and no debug information is available")
-    {
-        return false;
-    }
-
-    true
-}
-
 pub async fn post_events(
     Extension(app): Extension<Arc<AppState>>,
     TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,

crates/collab/src/api/ips_file.rs 🔗

@@ -1,346 +0,0 @@
-use anyhow::Context as _;
-use collections::HashMap;
-
-use semantic_version::SemanticVersion;
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-
-#[derive(Debug)]
-pub struct IpsFile {
-    pub header: Header,
-    pub body: Body,
-}
-
-impl IpsFile {
-    pub fn parse(bytes: &[u8]) -> anyhow::Result<IpsFile> {
-        let mut split = bytes.splitn(2, |&b| b == b'\n');
-        let header_bytes = split.next().context("No header found")?;
-        let header: Header = serde_json::from_slice(header_bytes).context("parsing header")?;
-
-        let body_bytes = split.next().context("No body found")?;
-
-        let body: Body = serde_json::from_slice(body_bytes).context("parsing body")?;
-        Ok(IpsFile { header, body })
-    }
-
-    pub fn faulting_thread(&self) -> Option<&Thread> {
-        self.body.threads.get(self.body.faulting_thread? as usize)
-    }
-
-    pub fn app_version(&self) -> Option<SemanticVersion> {
-        self.header.app_version.parse().ok()
-    }
-
-    pub fn timestamp(&self) -> anyhow::Result<chrono::DateTime<chrono::FixedOffset>> {
-        chrono::DateTime::parse_from_str(&self.header.timestamp, "%Y-%m-%d %H:%M:%S%.f %#z")
-            .map_err(|e| anyhow::anyhow!(e))
-    }
-
-    pub fn description(&self, panic: Option<&str>) -> String {
-        let mut desc = if self.body.termination.indicator == "Abort trap: 6" {
-            match panic {
-                Some(panic_message) => format!("Panic `{}`", panic_message),
-                None => "Crash `Abort trap: 6` (possible panic)".into(),
-            }
-        } else if let Some(msg) = &self.body.exception.message {
-            format!("Exception `{}`", msg)
-        } else {
-            format!("Crash `{}`", self.body.termination.indicator)
-        };
-        if let Some(thread) = self.faulting_thread() {
-            if let Some(queue) = thread.queue.as_ref() {
-                desc += &format!(
-                    " on thread {} ({})",
-                    self.body.faulting_thread.unwrap_or_default(),
-                    queue
-                );
-            } else {
-                desc += &format!(
-                    " on thread {} ({})",
-                    self.body.faulting_thread.unwrap_or_default(),
-                    thread.name.clone().unwrap_or_default()
-                );
-            }
-        }
-        desc
-    }
-
-    pub fn backtrace_summary(&self) -> String {
-        if let Some(thread) = self.faulting_thread() {
-            let mut frames = thread
-                .frames
-                .iter()
-                .filter_map(|frame| {
-                    if let Some(name) = &frame.symbol {
-                        if self.is_ignorable_frame(name) {
-                            return None;
-                        }
-                        Some(format!("{:#}", rustc_demangle::demangle(name)))
-                    } else if let Some(image) = self.body.used_images.get(frame.image_index) {
-                        Some(image.name.clone().unwrap_or("<unknown-image>".into()))
-                    } else {
-                        Some("<unknown>".into())
-                    }
-                })
-                .collect::<Vec<_>>();
-
-            let total = frames.len();
-            if total > 21 {
-                frames = frames.into_iter().take(20).collect();
-                frames.push(format!("  and {} more...", total - 20))
-            }
-            frames.join("\n")
-        } else {
-            "<no backtrace available>".into()
-        }
-    }
-
-    fn is_ignorable_frame(&self, symbol: &String) -> bool {
-        [
-            "pthread_kill",
-            "panic",
-            "backtrace",
-            "rust_begin_unwind",
-            "abort",
-        ]
-        .iter()
-        .any(|s| symbol.contains(s))
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(default)]
-pub struct Header {
-    pub app_name: String,
-    pub timestamp: String,
-    pub app_version: String,
-    pub slice_uuid: String,
-    pub build_version: String,
-    pub platform: i64,
-    #[serde(rename = "bundleID", default)]
-    pub bundle_id: String,
-    pub share_with_app_devs: i64,
-    pub is_first_party: i64,
-    pub bug_type: String,
-    pub os_version: String,
-    pub roots_installed: i64,
-    pub name: String,
-    pub incident_id: String,
-}
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Body {
-    pub uptime: i64,
-    pub proc_role: String,
-    pub version: i64,
-    #[serde(rename = "userID")]
-    pub user_id: i64,
-    pub deploy_version: i64,
-    pub model_code: String,
-    #[serde(rename = "coalitionID")]
-    pub coalition_id: i64,
-    pub os_version: OsVersion,
-    pub capture_time: String,
-    pub code_signing_monitor: i64,
-    pub incident: String,
-    pub pid: i64,
-    pub translated: bool,
-    pub cpu_type: String,
-    #[serde(rename = "roots_installed")]
-    pub roots_installed: i64,
-    #[serde(rename = "bug_type")]
-    pub bug_type: String,
-    pub proc_launch: String,
-    pub proc_start_abs_time: i64,
-    pub proc_exit_abs_time: i64,
-    pub proc_name: String,
-    pub proc_path: String,
-    pub bundle_info: BundleInfo,
-    pub store_info: StoreInfo,
-    pub parent_proc: String,
-    pub parent_pid: i64,
-    pub coalition_name: String,
-    pub crash_reporter_key: String,
-    #[serde(rename = "codeSigningID")]
-    pub code_signing_id: String,
-    #[serde(rename = "codeSigningTeamID")]
-    pub code_signing_team_id: String,
-    pub code_signing_flags: i64,
-    pub code_signing_validation_category: i64,
-    pub code_signing_trust_level: i64,
-    pub instruction_byte_stream: InstructionByteStream,
-    pub sip: String,
-    pub exception: Exception,
-    pub termination: Termination,
-    pub asi: Asi,
-    pub ext_mods: ExtMods,
-    pub faulting_thread: Option<i64>,
-    pub threads: Vec<Thread>,
-    pub used_images: Vec<UsedImage>,
-    pub shared_cache: SharedCache,
-    pub vm_summary: String,
-    pub legacy_info: LegacyInfo,
-    pub log_writing_signature: String,
-    pub trial_info: TrialInfo,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct OsVersion {
-    pub train: String,
-    pub build: String,
-    pub release_type: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct BundleInfo {
-    #[serde(rename = "CFBundleShortVersionString")]
-    pub cfbundle_short_version_string: String,
-    #[serde(rename = "CFBundleVersion")]
-    pub cfbundle_version: String,
-    #[serde(rename = "CFBundleIdentifier")]
-    pub cfbundle_identifier: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct StoreInfo {
-    pub device_identifier_for_vendor: String,
-    pub third_party: bool,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct InstructionByteStream {
-    #[serde(rename = "beforePC")]
-    pub before_pc: String,
-    #[serde(rename = "atPC")]
-    pub at_pc: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Exception {
-    pub codes: String,
-    pub raw_codes: Vec<i64>,
-    #[serde(rename = "type")]
-    pub type_field: String,
-    pub subtype: Option<String>,
-    pub signal: String,
-    pub port: Option<i64>,
-    pub guard_id: Option<i64>,
-    pub message: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Termination {
-    pub flags: i64,
-    pub code: i64,
-    pub namespace: String,
-    pub indicator: String,
-    pub by_proc: String,
-    pub by_pid: i64,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Asi {
-    #[serde(rename = "libsystem_c.dylib")]
-    pub libsystem_c_dylib: Vec<String>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct ExtMods {
-    pub caller: ExtMod,
-    pub system: ExtMod,
-    pub targeted: ExtMod,
-    pub warnings: i64,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct ExtMod {
-    #[serde(rename = "thread_create")]
-    pub thread_create: i64,
-    #[serde(rename = "thread_set_state")]
-    pub thread_set_state: i64,
-    #[serde(rename = "task_for_pid")]
-    pub task_for_pid: i64,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Thread {
-    pub thread_state: HashMap<String, Value>,
-    pub id: i64,
-    pub triggered: Option<bool>,
-    pub name: Option<String>,
-    pub queue: Option<String>,
-    pub frames: Vec<Frame>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Frame {
-    pub image_offset: i64,
-    pub symbol: Option<String>,
-    pub symbol_location: Option<i64>,
-    pub image_index: usize,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct UsedImage {
-    pub source: String,
-    pub arch: Option<String>,
-    pub base: i64,
-    #[serde(rename = "CFBundleShortVersionString")]
-    pub cfbundle_short_version_string: Option<String>,
-    #[serde(rename = "CFBundleIdentifier")]
-    pub cfbundle_identifier: Option<String>,
-    pub size: i64,
-    pub uuid: String,
-    pub path: Option<String>,
-    pub name: Option<String>,
-    #[serde(rename = "CFBundleVersion")]
-    pub cfbundle_version: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct SharedCache {
-    pub base: i64,
-    pub size: i64,
-    pub uuid: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct LegacyInfo {
-    pub thread_triggered: ThreadTriggered,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct ThreadTriggered {
-    pub name: String,
-    pub queue: String,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct TrialInfo {
-    pub rollouts: Vec<Rollout>,
-    pub experiments: Vec<Value>,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", default)]
-pub struct Rollout {
-    pub rollout_id: String,
-    pub factor_pack_ids: HashMap<String, Value>,
-    pub deployment_id: i64,
-}

crates/collab/src/api/slack.rs 🔗

@@ -1,144 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-/// https://api.slack.com/reference/messaging/payload
-#[derive(Default, Clone, Serialize, Deserialize)]
-pub struct WebhookBody {
-    text: String,
-    #[serde(skip_serializing_if = "Vec::is_empty")]
-    blocks: Vec<Block>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    thread_ts: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    mrkdwn: Option<bool>,
-}
-
-impl WebhookBody {
-    pub fn new(f: impl FnOnce(Self) -> Self) -> Self {
-        f(Self::default())
-    }
-
-    pub fn add_section(mut self, build: impl FnOnce(Section) -> Section) -> Self {
-        self.blocks.push(Block::Section(build(Section::default())));
-        self
-    }
-
-    pub fn add_rich_text(mut self, build: impl FnOnce(RichText) -> RichText) -> Self {
-        self.blocks
-            .push(Block::RichText(build(RichText::default())));
-        self
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-#[serde(tag = "type")]
-/// https://api.slack.com/reference/block-kit/blocks
-pub enum Block {
-    #[serde(rename = "section")]
-    Section(Section),
-    #[serde(rename = "rich_text")]
-    RichText(RichText),
-    // .... etc.
-}
-
-/// https://api.slack.com/reference/block-kit/blocks#section
-#[derive(Default, Clone, Serialize, Deserialize)]
-pub struct Section {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    text: Option<Text>,
-    #[serde(skip_serializing_if = "Vec::is_empty")]
-    fields: Vec<Text>,
-    // fields, accessories...
-}
-
-impl Section {
-    pub fn text(mut self, text: Text) -> Self {
-        self.text = Some(text);
-        self
-    }
-
-    pub fn add_field(mut self, field: Text) -> Self {
-        self.fields.push(field);
-        self
-    }
-}
-
-/// https://api.slack.com/reference/block-kit/composition-objects#text
-#[derive(Clone, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum Text {
-    #[serde(rename = "plain_text")]
-    PlainText { text: String, emoji: bool },
-    #[serde(rename = "mrkdwn")]
-    Markdown { text: String, verbatim: bool },
-}
-
-impl Text {
-    pub fn plain(s: String) -> Self {
-        Self::PlainText {
-            text: s,
-            emoji: true,
-        }
-    }
-
-    pub fn markdown(s: String) -> Self {
-        Self::Markdown {
-            text: s,
-            verbatim: false,
-        }
-    }
-}
-
-#[derive(Default, Clone, Serialize, Deserialize)]
-pub struct RichText {
-    elements: Vec<RichTextObject>,
-}
-
-impl RichText {
-    pub fn new(f: impl FnOnce(Self) -> Self) -> Self {
-        f(Self::default())
-    }
-
-    pub fn add_preformatted(
-        mut self,
-        build: impl FnOnce(RichTextPreformatted) -> RichTextPreformatted,
-    ) -> Self {
-        self.elements.push(RichTextObject::Preformatted(build(
-            RichTextPreformatted::default(),
-        )));
-        self
-    }
-}
-
-/// https://api.slack.com/reference/block-kit/blocks#rich_text
-#[derive(Clone, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum RichTextObject {
-    #[serde(rename = "rich_text_preformatted")]
-    Preformatted(RichTextPreformatted),
-    // etc.
-}
-
-/// https://api.slack.com/reference/block-kit/blocks#rich_text_preformatted
-#[derive(Clone, Default, Serialize, Deserialize)]
-pub struct RichTextPreformatted {
-    #[serde(skip_serializing_if = "Vec::is_empty")]
-    elements: Vec<RichTextElement>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    border: Option<u8>,
-}
-
-impl RichTextPreformatted {
-    pub fn add_text(mut self, text: String) -> Self {
-        self.elements.push(RichTextElement::Text { text });
-        self
-    }
-}
-
-/// https://api.slack.com/reference/block-kit/blocks#element-types
-#[derive(Clone, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum RichTextElement {
-    #[serde(rename = "text")]
-    Text { text: String },
-    // etc.
-}

crates/collab/src/lib.rs 🔗

@@ -153,7 +153,6 @@ pub struct Config {
     pub prediction_api_key: Option<Arc<str>>,
     pub prediction_model: Option<Arc<str>>,
     pub zed_client_checksum_seed: Option<String>,
-    pub slack_panics_webhook: Option<String>,
     pub auto_join_channel_id: Option<ChannelId>,
     pub supermaven_admin_api_key: Option<Arc<str>>,
 }
@@ -204,7 +203,6 @@ impl Config {
             prediction_api_key: None,
             prediction_model: None,
             zed_client_checksum_seed: None,
-            slack_panics_webhook: None,
             auto_join_channel_id: None,
             migrations_path: None,
             seed_path: None,

crates/collab/src/tests/test_server.rs 🔗

@@ -599,7 +599,6 @@ impl TestServer {
                 prediction_api_key: None,
                 prediction_model: None,
                 zed_client_checksum_seed: None,
-                slack_panics_webhook: None,
                 auto_join_channel_id: None,
                 migrations_path: None,
                 seed_path: None,