{"record":{"id":"8ccaaea3b79d4c9f","repo":"unicity-aos/aos-ce","slug":"subscribe-key-has-no-handler","errorCode":null,"errorMessage":"Subscribe `{key}` has no `handler`.","messagePattern":"Subscribe `(.+?)` has no `handler`\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"capsules/capsule-forge/src/checks.rs","lineNumber":269,"sourceCode":"                }\n            }\n        }\n\n        let Some(tool) = key.strip_prefix(\"tool.v1.execute.\") else {\n            continue;\n        };\n        // The `*.result` publish key would also strip; skip non-tool shapes.\n        if tool.contains('*') || tool.contains('.') {\n            continue;\n        }\n        saw_execute_tool = true;\n        let has_handler = sub_table\n            .and_then(|t| t.get(key))\n            .and_then(|v| v.get(\"handler\"))\n            .and_then(Toml::as_str)\n            .is_some_and(|h| !h.is_empty());\n        if !has_handler {\n            out.push(Finding::err(\n                format!(\"Subscribe `{key}` has no `handler`.\"),\n                format!(\"Add `handler = \\\"tool_execute_{tool}\\\"` to the `{key}` row.\"),\n            ));\n        }\n    }\n\n    if saw_execute_tool {\n        for required in [\"tool.v1.execute.*.result\", \"tool.v1.response.describe.*\"] {\n            if !pub_keys.iter().any(|k| k == required) {\n                out.push(Finding::err(\n                    format!(\"Mandatory publish key `{required}` is missing.\"),\n                    format!(\"Add `\\\"{required}\\\" = {{ wit = ... }}` to [publish]; tool results/describe break without it.\"),\n                ));\n            }\n        }\n        if !sub_keys.iter().any(|k| k == \"tool.v1.request.describe\") {\n            out.push(Finding::err(\n                \"Subscribe `tool.v1.request.describe` is missing — tools won't be discoverable.\",","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-forge/src/checks.rs#L251-L287","documentation":"Every [subscribe] entry must declare a non-empty `handler` string naming the export invoked when a message arrives on that topic. The check looks up `handler` as a non-empty string on the subscription row; if absent or empty, the subscription is unbound and the manifest is rejected with a suggested `tool_execute_<tool>` handler name.","triggerScenarios":"validate_manifest -> check_tool_bus iterates subscribe keys and finds a row where `handler` is missing, empty (\"\"), or not a string.","commonSituations":"Declaring an ACL-only subscription without a binding, forgetting the handler when adding a new topic, or renaming an export without updating the subscribe row.","solutions":["Add `handler = \"tool_execute_<tool>\"` (the matching export name) to the [subscribe] row.","If the subscription is ACL-only and intentionally unbound, remove it or remove the priority field per the sibling check.","Re-run validation to confirm."],"exampleFix":"// before ([subscribe])\n\"tool.v1.request.describe\" = { wit = ... }\n// after\n\"tool.v1.request.describe\" = { wit = ..., handler = \"tool_describe\" }","handlingStrategy":"validation","validationCode":"// Ensure every subscribe row has a non-empty handler before validation\nfor (k, row) in subscribe_table {\n    let h = row.get(\"handler\").and_then(|v| v.as_str()).unwrap_or(\"\");\n    if h.is_empty() { eprintln!(\"subscribe {k} missing handler\"); }\n}","typeGuard":"fn has_handler(row: &toml::Value) -> bool {\n    row.get(\"handler\").and_then(|v| v.as_str()).map_or(false, |h| !h.is_empty())\n}","tryCatchPattern":null,"preventionTips":["Use capsule-forge scaffolding so new subscriptions always include a handler.","Keep handler names paired with their exports (tool_execute_<tool>) in one template.","Run `capsule-forge validate` in CI before packaging."],"tags":["manifest","config","validation","missing-field"],"backgroundTag":"missing-required-config-field","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}