{"record":{"id":"24aa36d83007d892","repo":"jdx/mise","slug":"invalid-action-prediction-adapter","errorCode":null,"errorMessage":"invalid action prediction adapter","messagePattern":"invalid action prediction adapter","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/agent.rs","lineNumber":1811,"sourceCode":"        || !task\n            .bytes()\n            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))\n    {\n        bail!(\"invalid task action identity\");\n    }\n    Ok(())\n}\n\nfn validate_action_prediction(prediction: &ActionPrediction) -> Result<()> {\n    prediction.invocation.validate()?;\n    prediction.action.validate()?;\n    if prediction.adapter.is_empty()\n        || !prediction\n            .adapter\n            .bytes()\n            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))\n    {\n        bail!(\"invalid action prediction adapter\");\n    }\n    if prediction.payload.len() > MAX_ACTION_PREDICTION_PAYLOAD {\n        bail!(\"action prediction payload is too large\");\n    }\n    serde_json::from_str::<serde_json::Value>(&prediction.payload)?;\n    Ok(())\n}\n\nfn validate_task_manifest(manifest: &TaskActionManifest, task: &str) -> Result<()> {\n    if manifest.version != TASK_ACTION_MANIFEST_VERSION || manifest.task != task {\n        bail!(\"task action manifest has an invalid identity\");\n    }\n    if manifest.predictions.len() > MAX_TASK_ACTION_PREDICTIONS {\n        bail!(\"task action manifest contains too many predictions\");\n    }\n    let mut invocations = BTreeMap::new();\n    for prediction in &manifest.predictions {\n        validate_action_prediction(prediction)?;","sourceCodeStart":1793,"sourceCodeEnd":1829,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/agent.rs#L1793-L1829","documentation":"validate_action_prediction requires prediction.adapter to be a non-empty string of ASCII alphanumerics plus '-' and '_' only. Adapter names become part of recorded cache metadata, so free-form characters (spaces, dots, slashes, '@') are rejected.","triggerScenarios":"Recording a prediction with adapter like \"cargo build\" (space), \"rustc@1.80\" ('@'), \"go/build\" (slash), or \"\" (empty string).","commonSituations":"Deriving adapter names from arbitrary CLI strings or versioned labels; empty adapter due to a missing field in generated prediction payloads.","solutions":["Normalize adapter names to [A-Za-z0-9_-]+ before recording, e.g. \"cargo-build\" or \"rustc\"","Reject or default empty adapter fields upstream instead of sending them"],"exampleFix":"// before\nlet adapter = format!(\"{}@{}\", tool, version); // \"rustc@1.80\"\n// after\nlet adapter = format!(\"{}-{}\", tool, version).replace(|c: char| !c.is_ascii_alphanumeric() && c != '-' && c != '_', \"-\");","handlingStrategy":"validation","validationCode":"fn normalize_adapter(name: &str) -> String {\n    name.chars()\n        .map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '-' })\n        .collect()\n}\nlet adapter = normalize_adapter(&adapter);\nassert!(!adapter.is_empty());","typeGuard":"fn is_valid_adapter(adapter: &str) -> bool {\n    !adapter.is_empty()\n        && adapter.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_'))\n}","tryCatchPattern":null,"preventionTips":["Define adapter names from a fixed enum/allowlist ([A-Za-z0-9_-]) instead of free-form strings","Validate adapter fields client-side before recording predictions","Reject empty adapters at the source — they always fail this check"],"tags":["mise-cache","validation","identifier","adapter"],"backgroundTag":"invalid-identifier-format","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}