{"record":{"id":"e322d5243890e787","repo":"t8y2/dbx","slug":"a-batch-cancellation-token-is-always-available","errorCode":null,"errorMessage":"a batch cancellation token is always available","messagePattern":"a batch cancellation token is always available","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbx-core/src/agent_service.rs","lineNumber":804,"sourceCode":"        agents.iter().filter(|agent| agent.update_available).map(|agent| agent.db_type.clone()).collect();\n    let total = updatable.len() as u32;\n\n    // Use the command-scoped batch token when one was registered before the\n    // registry fetch + blocker check, so a cancel fired during that setup is\n    // observed. Otherwise register a token owned by this call keyed by a fresh\n    // operation id so it cannot collide with another in-flight operation.\n    let owned_operation_id: Option<String> =\n        if operation_id.is_some() { None } else { Some(uuid::Uuid::new_v4().to_string()) };\n    let effective_operation_id: &str = owned_operation_id.as_deref().or(operation_id).unwrap_or_default();\n    let owned_batch: Option<Arc<AgentInstallCancellation>> = if batch_cancellation.is_some() {\n        None\n    } else {\n        Some(am.begin_install_cancellation(&batch_cancellation_key(effective_operation_id)).await)\n    };\n    let active_batch_arc: Arc<AgentInstallCancellation> = owned_batch\n        .clone()\n        .or_else(|| batch_cancellation.cloned())\n        .expect(\"a batch cancellation token is always available\");\n    let active_batch: &AgentInstallCancellation = active_batch_arc.as_ref();\n    if active_batch.is_cancelled() {\n        if let Some(token) = owned_batch {\n            am.finish_install_cancellation(&batch_cancellation_key(effective_operation_id), &token).await;\n        }\n        return Ok(UpgradeAllAgentDriversResult { cancelled: total, ..Default::default() });\n    }\n\n    // Register a per-driver token for every driver in the batch, keyed by the\n    // batch operation id so per-driver cancels target this batch's driver even\n    // when the same db_type is being installed concurrently elsewhere. The\n    // batch token lets one click abort the whole upgrade; each driver token\n    // lets the user cancel a single driver while the rest continue.\n    let mut driver_cancellations = std::collections::HashMap::new();\n    for db_type in &updatable {\n        let key = batch_driver_cancellation_key(effective_operation_id, db_type);\n        let token = am.begin_install_cancellation(&key).await;\n        driver_cancellations.insert(db_type.clone(), token);","sourceCodeStart":786,"sourceCodeEnd":822,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/crates/dbx-core/src/agent_service.rs#L786-L822","documentation":"In upgrade_all_agent_drivers_with_registry, the code builds a batch cancellation token: either it owns one (created from begin_install_cancellation for the operation) or it receives one via batch_cancellation. The invariant is that one of the two is always Some, so .expect(\"a batch cancellation token is always available\") panics if both are None — a programming/contract violation, not a runtime condition.","triggerScenarios":"Calling upgrade_all_agent_drivers_with_registry (via upgrade_all_agent_drivers_from / _claimed, or the batch tests) with effective_operation_id = None AND batch_cancellation = None, i.e. an upgrade-all run that neither creates nor is handed a batch cancellation token.","commonSituations":"A refactor adds a new call site that passes no operation id and no shared batch token; tests invoke the registry upgrade path directly without going through the wrappers that always create the batch token; an operation-id plumbing bug makes effective_operation_id None in the batch path.","solutions":["Ensure every call path supplies either an operation id (so owned_batch is created) or a batch_cancellation token.","If a caller legitimately has neither, generate an operation id / call begin_install_cancellation before invoking.","Replace the expect with explicit error handling (return a generic token via begin_install_cancellation) if the invariant may not hold.","Add a debug_assert/test covering all public callers to keep the invariant enforced."],"exampleFix":"// before\nlet owned_batch = if cancelled_all { None } else {\n    Some(am.begin_install_cancellation(&batch_cancellation_key(effective_operation_id)).await)\n};\n// after\neffective_operation_id.get_or_insert_with(Uuid::new_v4);\nlet owned_batch = Some(am.begin_install_cancellation(&batch_cancellation_key(effective_operation_id)).await);","handlingStrategy":"validation","validationCode":"// before calling the batch upgrade API, ensure a token source exists\ndebug!(operation_id = ?effective_operation_id, has_batch = batch_cancellation.is_some());\nassert!(effective_operation_id.is_some() || batch_cancellation.is_some(),\n    \"batch upgrade requires an operation id or a batch cancellation token\");","typeGuard":"fn has_batch_token(op_id: Option<&OperationId>, token: Option<&AgentInstallCancellation>) -> bool {\n    op_id.is_some() || token.is_some()\n}","tryCatchPattern":"// Rust panics are not catchable with try/catch; use catch_unwind at the boundary only\nlet result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(||\n    upgrade_all_agent_drivers_from(...)\n));\nmatch result {\n    Ok(inner) => inner,\n    Err(_) => Err(\"batch upgrade panicked: missing cancellation token\".into()),\n}","preventionTips":["Always generate an operation id when none is supplied before invoking batch upgrade.","Centralize token creation in one wrapper instead of relying on each caller.","Add a unit test for the None/None input combination.","Prefer Result-returning constructors over expect for token resolution."],"tags":["rust","panic","invariant","cancellation","agent-upgrade"],"backgroundTag":"missing-cancellation-token-invariant","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}