unicity-aos/aos-ce · error
Subscribe ` ` has no `handler`.
Error message
Subscribe `{key}` has no `handler`. What it means
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.
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.
Example fix
// before ([subscribe])
"tool.v1.request.describe" = { wit = ... }
// after
"tool.v1.request.describe" = { wit = ..., handler = "tool_describe" } Defensive patterns
Strategy: validation
Validate before calling
// Ensure every subscribe row has a non-empty handler before validation
for (k, row) in subscribe_table {
let h = row.get("handler").and_then(|v| v.as_str()).unwrap_or("");
if h.is_empty() { eprintln!("subscribe {k} missing handler"); }
} Type guard
fn has_handler(row: &toml::Value) -> bool {
row.get("handler").and_then(|v| v.as_str()).map_or(false, |h| !h.is_empty())
} Prevention
- 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.
When it happens
Trigger: validate_manifest -> check_tool_bus iterates subscribe keys and finds a row where `handler` is missing, empty (""), or not a string.
Common situations: 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.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- Subscribe ` ` has priority outside the u32 range.
- Subscribe ` ` priority must be an integer.
- Mandatory publish key
- Subscribe `tool.v1.request.describe` is missing — tools…
- Topic ` ` has empty segments (leading/trailing/consecutive…
AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13).
Data as JSON: /api/errors/8ccaaea3b79d4c9f.
Report an issue: GitHub.
Appendix: source
Thrown at capsules/capsule-forge/src/checks.rs:269
}
}
}
let Some(tool) = key.strip_prefix("tool.v1.execute.") else {
continue;
};
// The `*.result` publish key would also strip; skip non-tool shapes.
if tool.contains('*') || tool.contains('.') {
continue;
}
saw_execute_tool = true;
let has_handler = sub_table
.and_then(|t| t.get(key))
.and_then(|v| v.get("handler"))
.and_then(Toml::as_str)
.is_some_and(|h| !h.is_empty());
if !has_handler {
out.push(Finding::err(
format!("Subscribe `{key}` has no `handler`."),
format!("Add `handler = \"tool_execute_{tool}\"` to the `{key}` row."),
));
}
}
if saw_execute_tool {
for required in ["tool.v1.execute.*.result", "tool.v1.response.describe.*"] {
if !pub_keys.iter().any(|k| k == required) {
out.push(Finding::err(
format!("Mandatory publish key `{required}` is missing."),
format!("Add `\"{required}\" = {{ wit = ... }}` to [publish]; tool results/describe break without it."),
));
}
}
if !sub_keys.iter().any(|k| k == "tool.v1.request.describe") {
out.push(Finding::err(
"Subscribe `tool.v1.request.describe` is missing — tools won't be discoverable.",View on GitHub (pinned to f6f22024fb)