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

  1. Add `handler = "tool_execute_<tool>"` (the matching export name) to the [subscribe] row.
  2. If the subscription is ACL-only and intentionally unbound, remove it or remove the priority field per the sibling check.
  3. 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

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


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)