{"record":{"id":"144097dc68bba781","repo":"Hmbown/CodeWhale","slug":"telemetry-is-disabled","errorCode":null,"errorMessage":"telemetry is disabled","messagePattern":"telemetry is disabled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/telemetry/src/envelope.rs","lineNumber":64,"sourceCode":"    /// When a flush was last *attempted*, RFC3339 UTC. Attempt, not success, so\n    /// a permanently offline machine tries at most once per interval.\n    #[serde(default)]\n    pub last_flush: Option<String>,\n}\n\n/// Read the install id, minting a fresh one if it is missing, unreadable,\n/// **not a UUID**, or older than [`ROTATION_DAYS`].\n///\n/// The UUID check is not a formatting nicety. `install_id` is the one\n/// envelope field read verbatim off disk into a batch, so without it the file\n/// is a free-form string slot on the wire for anything that can write\n/// `$CODEWHALE_HOME/telemetry/install_id.json`. Minting a fresh random id is\n/// always the safe direction — the cost is one rotation, and the docs already\n/// say no count derived from `install_id` is a user count.\npub fn read_or_create_install_id(root: &Path) -> Result<InstallId> {\n    buffer::try_with_lock(root, || {\n        if buffer::tombstone_present(root) {\n            anyhow::bail!(\"telemetry is disabled\");\n        }\n        let path = buffer::install_id_path(root);\n        let existing = std::fs::read_to_string(&path)\n            .ok()\n            .and_then(|body| serde_json::from_str::<InstallId>(&body).ok())\n            .filter(|record| uuid::Uuid::parse_str(record.install_id.trim()).is_ok())\n            .filter(|record| !is_expired(&record.rotated_at));\n        if let Some(record) = existing {\n            return Ok(record);\n        }\n        let record = InstallId {\n            schema_version: 1,\n            install_id: uuid::Uuid::new_v4().to_string(),\n            rotated_at: now_rfc3339(),\n        };\n        codewhale_config::persistence::atomic_write_json(&path, &record)\n            .with_context(|| format!(\"failed to write {}\", path.display()))?;\n        Ok(record)","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/telemetry/src/envelope.rs#L46-L82","documentation":"Thrown by read_or_create_install_id when the telemetry opt-out tombstone (the `disabled` marker file under <root>/telemetry) is present. The tombstone is the durable record of a telemetry opt-out; every envelope surface refuses to read or mint data while it exists. This is expected control flow, not a malfunction.","triggerScenarios":"Calling envelope::read_or_create_install_id(root) on a home where telemetry was wiped/opted out (buffer::wipe wrote the tombstone first and never removes it there), or any CODEWHALE_HOME whose telemetry directory contains the tombstone file. The check runs inside try_with_lock before install_id.json is ever read.","commonSituations":"A machine where the user disabled telemetry via settings or the wipe flow; CI or shared environments that pre-create the tombstone; unit tests pointing read_or_create_install_id at a tombstoned fixture home; a stray CODEWHALE_HOME env var pointing at an opted-out profile.","solutions":["Treat the error as expected: skip all telemetry work for this run and do not log it as a failure","If telemetry should be enabled, re-consent through the supported arm() flow (buffer::arm clears the tombstone for the current generation); never delete the tombstone by hand","Verify the root path / CODEWHALE_HOME you passed actually belongs to the profile you intend","Gate calls behind a tombstone/consent check so opted-out homes never reach this code path"],"exampleFix":"// before\nlet id = envelope::read_or_create_install_id(&root)?;\n// after\nif buffer::tombstone_present(&root) {\n    return Ok(None); // opted out: telemetry intentionally skipped\n}\nlet id = envelope::read_or_create_install_id(&root)?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match envelope::read_or_create_install_id(&root) {\n    Ok(id) => { /* proceed */ }\n    Err(err) if err.to_string() == \"telemetry is disabled\" => { /* opted out: skip telemetry silently */ }\n    Err(err) => return Err(err),\n}","preventionTips":["Treat 'telemetry is disabled' as control flow, not a failure; never log it as an error","Re-enable telemetry only via the re-consent (arm) flow — never delete the tombstone by hand","Check the consent decision before computing telemetry work so opted-out homes never reach the call","Point CODEWHALE_HOME at the intended profile before assuming a bug"],"tags":["telemetry","privacy","opt-out","configuration"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}