{"record":{"id":"2bfa822199bb3485","repo":"gitbutlerapp/gitbutler","slug":"invalid-handler-value","errorCode":null,"errorMessage":"Invalid Handler value","messagePattern":"Invalid Handler value","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-action/src/action.rs","lineNumber":84,"sourceCode":"    fn try_from(value: but_db::ButlerAction) -> Result<Self, Self::Error> {\n        let response = value\n            .response\n            .as_ref()\n            .and_then(|o| serde_json::from_str(o).ok());\n        let source = value\n            .source\n            .as_deref()\n            .and_then(|s| serde_json::from_str(s).ok())\n            .unwrap_or_default();\n        Ok(Self {\n            id: Uuid::parse_str(&value.id)?,\n            created_at: value.created_at,\n            external_prompt: value.external_prompt,\n            external_summary: value.external_summary,\n            handler: value\n                .handler\n                .parse()\n                .map_err(|_| anyhow::anyhow!(\"Invalid Handler value\"))?,\n            snapshot_before: gix::ObjectId::from_str(&value.snapshot_before)?,\n            snapshot_after: gix::ObjectId::from_str(&value.snapshot_after)?,\n            response,\n            error: value.error,\n            source,\n        })\n    }\n}\n\nimpl TryFrom<ButlerAction> for but_db::ButlerAction {\n    type Error = anyhow::Error;\n\n    fn try_from(value: ButlerAction) -> Result<Self, Self::Error> {\n        let response = value\n            .response\n            .as_ref()\n            .and_then(|o| serde_json::to_string(o).ok());\n        let source = serde_json::to_string(&value.source).ok();","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-action/src/action.rs#L66-L102","documentation":"The From/TryInto conversion building a ButlerAction from its persisted representation parses the handler string into the Handler type. An unparseable handler value makes the conversion fail with 'Invalid Handler value', guarding against corrupt or unknown action records in the database.","triggerScenarios":"Reading a butler_actions row whose handler column contains a string that Handler::from_str (parse()) rejects — unknown handler name, empty string, or hand-edited/corrupt db content.","commonSituations":"Schema/version drift: an older or newer build wrote handler names this build doesn't know; manual DB edits; corrupted rows after a crash; copying db files between app versions.","solutions":["Check the handler string stored in the butler_actions row and correct it to a valid Handler variant name","Upgrade/downgrade to the app version that wrote the record so the handler parses","Skip or delete the offending rows (list_actions already filters such rows) to keep the app working","If reproducible, extend Handler::from_str to accept the new handler spelling"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// treat persisted rows as fallible\nlet action: Option<ButlerAction> = row.try_into().ok(); // skip invalid rows like list_actions does","typeGuard":"fn is_valid_handler(h: &str) -> bool {\n    h.parse::<Handler>().is_ok()\n}","tryCatchPattern":"match ButlerAction::try_from(row) {\n  Ok(a) => handle(a),\n  Err(e) if e.to_string().contains(\"Invalid Handler value\") => {\n    log::warn!(\"skipping action with unknown handler\"); // best-effort\n  }\n  Err(e) => return Err(e),\n}","preventionTips":["Never hand-edit the butler_actions table","Keep app versions consistent with the db they read","Wrap row conversions in ok()/filter_map for best-effort listing","Extend Handler parsing when adding new handler names"],"tags":["serialization","database","parse","action"],"backgroundTag":"invalid-enum-value","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}